Textarea

Takes several lines of text.

Sizing

  • rowsdefault

  • autoGrow

States

  • rest

  • focus

  • 4 left

    Near the character limit

    near limit

  • 6 over the 16 limit

    Over the 16 character limit

    over limit

  • disabled

  • read-only

Usage

When to use it
Use
The answer runs past one line: a system prompt, instructions, feedback on a run, a reason.
Use
The text has a soft length limit the person should see coming (limit).
Not for
The answer fits on one line. Use Input.
Not for
The person chats with an agent: use the prompt input, which carries send and attachments.
Not for
The text is code or JSON to read: show it in a code block.

Rules

7 rules
Must
Put a Textarea in a Field with a FieldLabel. Because a placeholder vanishes on the first keystroke and is not a reliable accessible name.
Must
Use limit for a length cap, never maxLength. Because maxLength cuts a paste without a word, while limit keeps every character, counts down near the end and marks the field invalid past it.
Never
Make a Textarea a pill or change its corner. Because pills are for one line; a box of text takes the 10px overlay corner, and a pill would clip its first and last lines.
Never
Draw a ring, a black line or a shadow to show focus. Because a field shows focus by darkening its own line to ink at about a third, and a ring reads as a pressable thing.
Should
With autoGrow, set maxRows so a long paste scrolls inside the box instead of pushing the form off the screen.
Should
Leave resizable off when autoGrow is on; the box already fits its text. Because a dragged height fights the grown one on the next keystroke.
Should
Make the placeholder an example of a good answer, not the label again.

State order

7 states, highest first
1
disabled
2
invalid
3
over-limit
4
focus
5
near-limit
6
read-only
7
rest

Accessibility

4 checks
A native textarea named by FieldLabel, with aria-invalid and aria-describedby from the Field.
With limit, the counter joins aria-describedby, and a status region announces only crossing 80% and the limit, never every keystroke.
Focus darkens the line to --line-focus beside the caret; invalid adds the error sentence, so neither rests on colour alone.
Text is 16px under a coarse pointer, so a phone browser never zooms on focus.

Keyboard

2 keys
Enter
Adds a line; it never submits the form.
Tab
Leaves the textarea; it never types a tab.

Edge cases

6 edge cases
Paste over the limit
Keeps every character, counts how far over in the failure word and marks the field invalid.
Long paste with autoGrow
Grows to maxRows, then scrolls inside the box.
Browser without field-sizing
autoGrow measures scrollHeight on every input instead.
Narrow container
The width follows the container; only the height follows the text.
Right to left
Text and the counter align to the reading direction.
Dark theme
Paper turns near-black and the line stays ink at 15%.

API

7 props
rows
number default 3
autoGrow
boolean default false
minRows, maxRows
number default 3, 10
limit
number
resizable
boolean default false
onValueChange
(value: string) => void
aria-invalid
boolean

Tokens

18 tokens
--paper
#ffffff
--field
#efefef
--line-control
rgb(5 5 5 / 0.15)
--line-focus
rgb(5 5 5 / 0.32)
--line-faint
rgb(5 5 5 / 0.06)
--fail
#e35e49
--fail-text
#b23c29
--text-1
rgb(5 5 5 / 0.80)
--text-2
rgb(5 5 5 / 0.60)
--text-3
rgb(5 5 5 / 0.45)
--text-disabled
rgb(5 5 5 / 0.32)
--radius-overlay
10px
--type-body-sm
400 13px/1.5 var(--font-sans)
--type-caption
400 12px/1.35 var(--font-sans)
--space-2
8px
--space-3
12px
--dur-state
160ms
--ease-out
cubic-bezier(0.2, 0.8, 0.2, 1)

Install

For your coding agent

GitHub

Please star me pookie

141

A new design system every week

Leave your email before you copy the prompt.