Components/Form
TextArea
Text areas collect several lines of typed text. They suit an answer whose length the reader decides — a description, a note, a message — and they grow with what is written, a line at a time, so a short answer is not framed by an empty box and a longer one is not squeezed into a single line.
import { TextArea } from '@growth-protocol-ai/alpha';When to use
Reach for a text area when the answer is prose the reader composes and its length is theirs to decide — a description, a reason, a message. Give it a visible label, and let it open at about the length the question deserves: a field that opens tall asks for more words than you meant to ask for, and one that opens short makes a considered answer feel unwelcome.
When not to use
Do not use a text area for an answer that is one line by nature — a name, an email address, a web address. Those belong in a text input, and the test is not how long the answer runs but whether a line break could ever belong inside it. An answer that is one of a known set is a dropdown or a radio group, and a question the page itself is meant to answer is a search field.
size2 values
Size is geometry only — it never changes what the field means. Both open at two lines and stop three lines later, so what a size buys is not capacity but the type the text is set in and the room around it.
| size | When to use |
|---|---|
regular | 14/24 in a 240px frame — the single-line field’s width, so the two line up in a form without arithmetic. |
large | 16/28 in a 320px frame. Hero forms and touch-first surfaces, and the better choice for anything long enough to be re-read. |
| State | When to use |
|---|---|
Empty | The default frame around a placeholder in --text-disabled. Focus turns the border --button-primary and moves nothing else, so the field being typed into is the only one lit on the page. |
Filled | Content in --text-typical, and a frame that has not changed. The single-line field paints a brand border once it holds something; a paragraph does not need telling, because the words are the signal — so here the text colour carries it alone while the field grows a line at a time to its cap and then scrolls. |
Display (read-only) | Prose the reader may read, select and copy but not change. Not a disabled control — nothing about it is off, it simply is not taking input — so it keeps the default frame, stays focusable and is still submitted. Confirmation screens and summary views. |
Disabled, empty | A --surface-depth-3 fill under the same border, with the pointer refused. The field is skipped by the keyboard and left out of the submitted form, so nothing in it is waiting for the reader. |
Disabled, filled | The same fill and the same border: content changes nothing here, exactly as it changes nothing about an enabled field. Only the text moves, to --text-disabled, so locked prose reads as locked rather than as an answer someone abandoned. |
Give every field a name. A placeholder is not one: it leaves the moment someone types, and a field whose only name was its placeholder is anonymous from then on. Pass an id and point a <label htmlFor> at it, or set aria-label.
Reach for this when a line break could belong in the answer. A name, an email or a web address belongs in the single-line field, which is a different control rather than a shorter one.
The component is the textarea, so every prop lands on it: id, name, required, maxLength and aria-describedby all go where a label, a form library and a screen reader expect to find them, and [data-slot="text-area"] selects the control itself rather than a frame around it.
Nothing counts characters on screen. Where a limit is worth knowing before it bites, render your own counter beside the field and point the field at it with aria-describedby, so it is heard as well as seen.
Think twice before raising rows. A field that opens tall and is never filled reads as a demand for more words than the question needs, and a reader who sees a big box tends to answer until it looks full.
There is no drag handle, because the field already sizes itself to what is in it. Where a surface wants a different cap, move max-height through className — and give every field in that form the same one.
readOnly for prose the reader may read and copy, disabled for a control that is switched off. A disabled field is skipped by the keyboard and left out of the submitted form; a read-only one is neither, which is usually what a confirmation screen actually wants.
The frame says nothing about content, and nothing about validity either. Validation is not in this iteration, and an error state should arrive as a documented variant rather than as a red border passed through className.
Controlled value
value with onChange, where the state lives in yours. Nothing is debounced and nothing is buffered — the field reports every keystroke — which is worth knowing before a long answer is wired to something that re-renders a page.
In a form column
A field that belongs to a form rather than to a toolbar. fullWidth drops the size’s fixed width so it fills the column its siblings share, and rows raises the floor where the answer is expected to run long — the cap follows three lines above wherever the floor is, so the field still has the same amount of growing to do.
| Prop | Type | Default | Notes |
|---|---|---|---|
size | "regular" | "large" | "regular" | 14/24 in 240px, or 16/28 in 320px. Geometry only. |
fullWidth | boolean | false | Fill the container instead of the size’s fixed width. For a form whose fields share a column. |
rows | number | 2 | Lines the field opens at. The cap follows three above it, so rows={4} is a field that runs four lines to seven. |
value | string | number | — | Controlled value. Pair it with onChange. |
defaultValue | string | number | — | Initial value when uncontrolled. The field opens at the height that content needs, up to the cap. |
placeholder | string | — | Shown while the field is empty, in --text-disabled. A hint, not a name. |
disabled | boolean | false | Non-interactive, and repainted rather than faded. |
readOnly | boolean | false | The Display state. Content, no editing, and none of disabled’s consequences. |
children | never | — | A <textarea> takes none — React warns on them. The text goes in value or defaultValue, and the label beside the field. |