Textarea
Multi-line field control for issue bodies and comments.
Textarea is the multi-line sibling of Input, for comments,
descriptions, and notes. It sits on the same bg-surface with the same border, corners,
and focus ring, starts 96px tall, and grows with its text instead of scrolling inside
itself. Pair it with Field for a label, a description, and
an error.
Markdown is fine. Keep it to the work, not the process.
Usage
import { Field, FieldDescription, FieldLabel } from '@/components/ui/field';
import { Textarea } from '@/components/ui/textarea';
<Field>
<FieldLabel htmlFor="body">Description</FieldLabel>
<Textarea id="body" aria-describedby="body-help" />
<FieldDescription id="body-help">Markdown is fine.</FieldDescription>
</Field>Textarea takes every <textarea> attribute and adds no props of its own, and a ref
reaches the <textarea>. Like Input, its border is border-stroke, its corners follow the
look's rounded, and its text is font-sans at the look's UI weight and 16px at every
width, so iOS Safari does not zoom the page in when it takes focus.
It grows with the text
field-sizing-content sizes the box to what is typed, from a 96px minimum with no maximum,
so a long entry makes the page longer rather than scrolling inside the box. Add a max-h-*
class to cap it, and past that height it scrolls. Set a different min-h-* for a taller
resting height. The resize handle stays, so a person can still drag it taller, and in a
browser without field-sizing it holds its height and scrolls. Type a few lines below to
watch it grow and then scroll past 192px.
Invalid and disabled
Set aria-invalid when the value fails validation. The border turns border-destructive,
the same as on Input, Select, and Checkbox, and a screen reader announces the field as
invalid. Say what is wrong in a
FieldError below it and list the
error's id in aria-describedby, so the message is announced when it appears and read with
the field after that.
disabled drops the field to 64% opacity with a not-allowed cursor, and a Field around it
dims its label to match. readOnly has no style of its own, so a read-only Textarea looks
the same as one that can be edited.
Accessibility
- It renders a native
<textarea>, so Enter starts a new line rather than submitting the form, and Tab moves focus out of the field. - It has no name of its own. Name it with a FieldLabel whose
htmlFormatches itsid, or witharia-labelwhen there is no visible label. A placeholder is not a label, since it disappears as soon as someone types. The placeholder is intext-fg-subtle, a hint the label does not depend on, so it does not read as a typed value. When it shows an example, start it with "e.g." or put the example in a FieldDescription. - Focus shows a 2px ring in the contrast ink, flush against the border, whenever the field has focus. In forced colors the ring is dropped and a transparent 2px outline takes its place, which the system paints.
aria-invalidpaints the borderborder-destructiveand makes a screen reader announce the field as invalid. Forced colors repaints the border in a system color, so the FieldError message is the sign that holds up there.disabledsets the native attribute, so the field leaves the tab order and assistive tech reports it as unavailable.- It is at least 96px tall and as wide as its container.
- It has no transitions, so it grows and changes color at once with or without reduced motion.
When to use something else
- Input for a single line, like a name, an email address, or a title.
- Markdown editor for notes with headings, lists, and links that save as markdown.
Install
shadcn add @gesso/textarea