Components / Controls
TextArea
Multi-line text input. It starts at rows, grows with its content up to maxRows, then scrolls. For a single line, use TextField.
When to useLink to "When to use"
- Notes, comments, descriptions and other free text longer than a line.
- A message or prompt with mentions (
@name,#channel, a table name). - Input where a live character count helps (limits, annotations).
Use something else when:
UsageLink to "Usage"
import { TextArea } from "@arshad-shah/cynosure";
<TextArea label="Notes" name="notes" rows={3} maxRows={10} />
<TextArea label="Bio" maxLength={280} showCount value={bio} onChange={setBio} />
<TextArea label="Message" description="Plain text only" error={error} />onChange receives the string value. Defaults are rows={3} and maxRows={12}.
Character counterLink to "Character counter"
showCount adds a live counter. With maxLength it reads "n / max", otherwise "n characters". Override the text with labels.count(count, max).
ResizingLink to "Resizing"
A grip in the bottom corner lets pointer and touch users drag the height. The box keeps the chosen height and still grows with content, up to maxRows (or the chosen height, if that is taller). Pass resizable={false} to remove the grip. Disabled areas have none.
MentionsLink to "Mentions"
Give a trigger character and suggestions, and typing the trigger at the start of a word opens a list of suggestions for the text that follows it, up to the caret (no spaces). The arrow keys move through it, Enter or Tab (or a click or tap) puts the chosen value and a space in place of the trigger and the query, and Escape closes it for that mention. onMention(item) is called after the insertion, which is an ordinary edit: onChange hears it and Undo takes it back.
<TextArea
label="Message"
trigger="@"
suggestions={[{ value: "@ada", label: "Ada Lovelace", description: "Engineering" }, "@alan"]}
onMention={(item) => track(item.value)}
/>
<TextArea label="Ask" trigger="@" suggestions={(query) => searchTables(query)} />suggestions is a list (strings, or items with a value, a label and a description), filtered by whether the value or a string label contains the query; or a function of the query that returns items or a promise of them. While a promise is pending the list says so (labels.loading) and is aria-busy; an answer for an older query is ignored, and a failed one closes the list. Nothing matching closes it too.
The list drops below the whole box, at least as wide as it, and moves above it when there is no room below (a chat input at the bottom of a window). It is the same list as Select, Combobox and TagInput use (OptionList).
OptionsLink to "Options"
variant:outline(default) orsoft.size:sm,mdorlg.labelHidden: hides the label visually while it still names the field. Prefer a visible label.- Native textarea attributes such as
name,required,disabled,readOnlyandmaxLengthpass through.
PropsLink to "Props"
| Prop | What it does | Type | Default |
|---|---|---|---|
labelRequired | The field's label and accessible name. | ReactNode | |
labelHidden | Visually hide the label (it still names the control). Prefer visible labels. | boolean | |
description | Help text shown below the field. | ReactNode | |
error | Error message shown below the field; it is announced and marks the field invalid. | ReactNode | |
value | The text (controlled). | string | |
defaultValue | Starting text when uncontrolled. | string | |
onChange | Called with the new text on every edit. | ((value: string) => void) | |
resizable | Show a corner grip to drag the height (pointer and touch). Content growth still applies. | boolean | |
rows | Minimum visible rows. | number | |
maxRows | Auto-grow cap; content beyond scrolls. | number | |
showCount | Shows a live character counter. | boolean | |
size | Text size of the field and its label. Default "md". | Size | "md" |
variant | outline or soft (tinted, borderless until focus). Default "outline". | "soft" | "outline" | "outline" |
trigger | Starts a mention: typed at the start of a word (such as "@"), it opens the suggestions list for the text that follows, up to the caret. | string | |
suggestions | What the trigger offers: items filtered by the query (their value or string label contains it), or a function of the query returning items or a promise of them (stale answers are ignored). | TextAreaSuggestions | |
onMention | Called with the suggestion chosen, after it is inserted. | ((item: TextAreaMention) => void) | |
labels | Replace the built-in English text: the character counter and the suggestion list's loading note. | Partial<TextAreaLabels> | |
className | Added to the root element, for layout. | string | |
style | Layout only. | CSSProperties | |
layout | stack puts the label above the control and the description and error below it. split puts the label and description at the start and the control at the end of one row (a settings row); when the field is too narrow for both, the control moves under the text. Default "stack". | "stack" | "split" | "stack" |
width | Width of the control, so the controls of a column of fields line up: "sm", "md" and "lg" are the --cy-field-control-width-* tokens (never wider than the field), "full" is the whole field. Default: the control's own width (the whole field in the stacked layout for TextField and the other fields). | FieldWidth |
Generated from the type definitions in @arshad-shah/cynosure 0.5.0.
Behaviour and accessibilityLink to "Behaviour and accessibility"
- Label, description and error are linked to the textarea through
Field. - Counter: announced politely (
aria-live="polite") and linked witharia-describedby. - Grip: a pointer and touch affordance only. It is
aria-hidden, so keyboard users get content growth instead. It stays tucked in the corner on every device, and the text keeps a bottom margin so the last line never runs under it. - Forms: works with
FormData,required, and reset restores the value, the count and the height. - Mentions: with
triggerandsuggestionsthe textarea hasaria-autocomplete="list"andaria-haspopup="listbox"; while the list is open,aria-controlsnames it andaria-activedescendantthe active option, so focus stays in the text. The list is named by the field's label, as Combobox's and TagInput's are. A textarea cannot take the combobox role oraria-expanded(ARIA in HTML), so the list being open is told byaria-controlsand the active option. The keys the list uses while open (arrows, Enter, Tab, Escape) are not passed to youronKeyDown, so an "Enter sends" handler does not fire when Enter picks a suggestion; Shift+Enter still makes a new line. Nothing happens while an IME composes: the query is read when the composition ends, and its Enter belongs to the IME. Escape closes only the list, not a dialog or popover around the field.
| Key (list open) | Result |
|---|---|
| Arrow Down / Arrow Up | Next or previous suggestion (stops at the ends) |
| Enter / Tab | Inserts the active suggestion and a space |
| Escape | Closes the list for this mention |
ThemingLink to "Theming"
Tokens: --cy-text-area-{radius,padding-inline,padding-block,bg,border,soft-bg}.
createTheme({ components: { textArea: { radius: "10px", paddingBlock: "10px" } } });RelatedLink to "Related"
These examples run Cynosure 0.5.0 (commit 50ce30b), the version on npm.