Textarea
Collect text that may span more than one line.
Overview
Textarea collects free-form text that can outgrow one line: descriptions, messages, notes, code snippets. It is the multi-line sibling of Input and shares its contract — a permanent label above, placeholder showing format only, native attributes passed straight through.
Use it when people will type more than roughly a sentence. For anything shorter, an Input respects their time; a tall empty box implies an essay is expected.
Anatomy
A textarea has a value, optional placeholder, size, variant, and state. Unlike Input there is no type — but maxLength, minLength, required, disabled, readOnly, and name all pass through natively.
Behavior
Growth. The box starts at a fixed height and scrolls once content exceeds it; it does not grow automatically. If watching all of their text matters (short comments), size it generously up front rather than promising auto-grow the component does not do.
Resize. The browser's resize handle stays available. Constrain it with resize classes if the layout truly cannot flex — but a textarea nobody may enlarge is usually a layout bug wearing a component.
Line handling. Enter inserts newlines; text wraps within the box. Values include those newline characters, so trim and normalize on submit where your backend cares.
States
Disabled skips focus, blocks editing, and excludes the value from submission. Read-only keeps the content selectable and copyable while still submitting — prefer it for computed or imported text that must travel with the form. Invalid follows aria-invalid: danger border plus an error message linked through Field or aria-describedby that says what to fix.
Placeholder styling matches Input: muted foreground tokens that adapt to dark mode automatically.
Accessibility
The visible label names the field (Label + htmlFor). Standard textarea keys apply — arrows move within the text, Enter adds lines, Tab leaves (it does not insert a tab character). Focus shows as a border change using accent tokens.
Height clears comfortable touch sizing, and the resize handle gives pointer users another way to fit their content. Long values scroll vertically rather than truncating; nothing a person typed is ever visually cut off without a way to reach it. RTL text mirrors correctly because padding uses logical properties.
Installation
Usage
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";<Label htmlFor={id}>Release notes</Label>
<Textarea id={id} name="notes" rows={4} maxLength={500} />Don't do this
Counting characters only after they are lost
// Bad
<Textarea maxLength={200} />
// no counter anywhere// Good
<Textarea maxLength={200} aria-describedby="count" />
<p id="count" aria-live="polite">
{200 - value.length} characters left
</p>A silent maxLength eats keystrokes: past the limit, typing does nothing and the person concludes the keyboard broke. Announce remaining characters politely so the wall arrives before they hit it — and let the counter say what still fits, not just that they failed.
Disabling spellcheck by default
// Bad
<Textarea spellCheck={false} />// Good
<Textarea /> // browser default: spellcheck on for proseProse fields benefit from underlines pointing at typos; turning them off wholesale assumes every entry is a URL. Opt out per-field for identifiers and code, not for every description box on the page.
Examples
Disabled and read-only states
Inside a form
With label
API reference
Accepts all native <textarea> props. Honest UI additions:
Validation state follows aria-invalid; disabled and read-only use the native attributes.