Textarea
Several lines of text. The Input's glass squircle field, growing with what's typed from three lines to about eleven, then scrolling. Built on the vendored textarea; every native attribute passes through.
Playground
import { Textarea } from "@/components/shared/textarea";
<Textarea
placeholder="Allergies, a reference photo, anything the stylist should know"
aria-label="Booking note"
/>In a field
Like any control, it takes a FieldLabel and, when it helps, a description of what the text is for. Type a few lines — the field grows to fit, then scrolls.
The agent uses this to answer questions about you. Parking, the entrance, what to bring.
import {
Field,
FieldDescription,
FieldLabel,
} from "@/components/shared/field";
import { Textarea } from "@/components/shared/textarea";
<Field>
<FieldLabel htmlFor="st-description">About the venue</FieldLabel>
<Textarea id="st-description" aria-describedby="st-description-hint" />
<FieldDescription id="st-description-hint">
The agent uses this to answer questions about you. Parking, the entrance, what to bring.
</FieldDescription>
</Field>With a limit
maxLength stops the text at the limit; say how much is left in the description so the limit never comes as a surprise.
280 characters left
import {
Field,
FieldDescription,
FieldLabel,
} from "@/components/shared/field";
import { Textarea } from "@/components/shared/textarea";
const [note, setNote] = useState("");
<Field>
<FieldLabel htmlFor="bk-note">Note for Lucia</FieldLabel>
<Textarea
id="bk-note"
value={note}
maxLength={280}
onChange={(e) => setNote(e.target.value)}
aria-describedby="bk-note-count"
/>
<FieldDescription id="bk-note-count">
{280 - note.length} characters left
</FieldDescription>
</Field>One line that wraps
size="sm" starts one line tall, as an sm Input beside it, and grows as its words wrap — for a short text in a row of Inputs that would otherwise be cut off on a phone, as a service's name on ServicesCard. It stays one line of text: Enter saves rather than breaking the line.
import { Textarea } from "@/components/shared/textarea";
import { Input } from "@/components/shared/input";
<div className="flex items-start gap-2">
<Textarea size="sm" rows={1} aria-label="Name" className="min-w-0 flex-1" />
<Input size="sm" aria-label="Minutes" className="w-14 shrink-0" />
<Input size="sm" aria-label="Price" className="w-16 shrink-0" />
</div>Invalid & disabled
Invalid is aria-invalid with a FieldError that says what to change; disabled dims the field.
import { Field, FieldError, FieldLabel } from "@/components/shared/field";
import { Textarea } from "@/components/shared/textarea";
<Field>
<FieldLabel htmlFor="st-description">About the venue</FieldLabel>
<Textarea id="st-description" defaultValue="Call us." aria-invalid aria-describedby="st-description-error" />
<FieldError id="st-description-error">Add a sentence or two — where you are and what you do.</FieldError>
</Field>
<Field>
<FieldLabel htmlFor="bk-note">Booking note</FieldLabel>
<Textarea id="bk-note" defaultValue="Booked by phone." disabled />
</Field>API reference
| Prop | Type | Default | Description |
|---|---|---|---|
| size | "sm" | "md" | "md" | md: three lines to about eleven. sm: one line at an sm Input's 32px, growing as its words wrap. |
| aria-invalid | boolean | false | Marks the text wrong: a destructive edge. Pair with a FieldError and aria-describedby. |
| maxLength | number | — | Stops the text at this many characters. Show what's left in a FieldDescription. |
| rows | number | — | The starting height, only where the browser can't size the field to its content. |
| disabled | boolean | false | Disables and dims the field. |
| className | string | — | Layout only — width, margins, a taller min-h. Never restyle the field here. |
Everything else passes through to the <textarea>: id, name, value, onChange, placeholder, readOnly, ref, …
Guidelines
- Reach for Textarea when the answer is sentences — a venue description, a note for the stylist. A name, a phone number or a slug is an Input, even when it could be long.
- Label it like any field, and use the placeholder for an example of a good answer, not instructions.
- Enter makes a new line. Submit with the form's button; ⌘/Ctrl+Enter may submit as a shortcut, never plain Enter.
- If there's a limit, set
maxLengthand count down in the description from the start — don't reveal it only when it's hit.