Gesso
Forms

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 htmlFor matches its id, or with aria-label when there is no visible label. A placeholder is not a label, since it disappears as soon as someone types. The placeholder is in text-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-invalid paints the border border-destructive and 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.
  • disabled sets 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

On this page