Textarea
Multi-line text input matching Input's border and focus. Resizes vertically only. Minimum height 64px (min-h-16), rounded-md, no shadow.
Anatomy
Explain in one sentence what this rule does.
- 1Label
Describes what the user should type. Never hidden — always above the textarea.
- 2Textarea
The multi-line field. Same flat border-input and focus ring as Input.
- 3Helper or counter
Optional muted line below. Helper text explains, character counter reassures on length limits.
Form field
<div className="flex flex-col gap-2 w-full max-w-md">
<Label htmlFor="notes">Rule summary</Label>
<Textarea
id="notes"
placeholder="Block payments from country XX when amount > 500 USD…"
rows={4}
/>
</div>Sizes
Height is controlled via the rows attribute or a min-h utility. Common presets: rows={2} for short notes, rows={4} for a paragraph, rows={8} for a long description.
<Textarea rows={2} placeholder="Short note (2 rows)" />
<Textarea rows={4} placeholder="Paragraph (4 rows)" />
<Textarea rows={8} placeholder="Long description (8 rows)" />States
Default / focus / disabled / readonly / error. Errors add border-destructive + helper text below.
<Textarea rows={3} placeholder="Explain what this rule does…" /><Textarea rows={3} disabled placeholder="Managed by admin" /><Textarea rows={3} readOnly value="…" />Summary is required.
<Textarea rows={3} aria-invalid
className="border-destructive focus-visible:ring-destructive/30" />
<p className="text-xs text-destructive">Summary is required.</p>With character counter
For fields with a max length, compose a small muted counter below the textarea. Turn destructive when the user is over the limit.
Required and optional
Yuno marks the odd one out. If most fields in a form are required, mark the few Optional ones with a small muted 'Optional' to the right of the Label. If most are optional, mark the required ones with a discrete 'Required' in the same spot. Never mix asterisks and Optional labels in the same form.
<div className="grid gap-1.5 w-full max-w-md">
<div className="flex items-center justify-between">
<Label htmlFor="notes">Notes</Label>
<span className="text-xs text-muted-foreground">Optional</span>
</div>
<Textarea id="notes" rows={3} placeholder="Add any extra context…" />
</div><div className="grid gap-1.5 w-full max-w-md">
<div className="flex items-center justify-between">
<Label htmlFor="summary">Rule summary</Label>
<span className="text-xs text-muted-foreground">Required</span>
</div>
<Textarea id="summary" rows={3} required />
</div>Recipes
Ready-to-copy compositions covering the most common Yuno usages of this atom.
Textarea + live character counter for a short business explanation with a max length.
<div className="grid gap-1.5 w-full max-w-md">
<Label htmlFor="summary">Rule summary</Label>
<Textarea id="summary" rows={3} placeholder="Explain what this rule does…" />
<div className="text-right text-xs text-muted-foreground">0 / 140</div>
</div>Textarea with a longer min-h for open-ended feedback + helper text describing the intent.
Sent anonymously to the team.
<div className="grid gap-1.5 w-full max-w-md">
<Label htmlFor="feedback">Feedback</Label>
<Textarea id="feedback" rows={6} placeholder="What could be better?" />
<p className="text-xs text-muted-foreground">Sent anonymously to the team.</p>
</div>System-generated multi-line message shown as read-only for the user's reference.
<div className="grid gap-1.5 w-full max-w-md">
<Label htmlFor="statement">Merchant statement</Label>
<Textarea id="statement" rows={3} readOnly value="…" />
</div>Import
Copy this import line at the top of the file where you compose this atom.
import { Textarea } from "@/components/ui/textarea";
import { Label } from "@/components/ui/label";Props
Everything else from the underlying HTML or Radix primitive is forwarded via ...props.
| Prop | Type | Default | Description |
|---|---|---|---|
| rows | number | 2 | Sets the visible line count. Common presets: 2 / 4 / 8. |
| value / defaultValue | string | — | Controlled or uncontrolled value. Controlled needs onChange. |
| placeholder | string | — | Hint text shown when the textarea is empty. |
| disabled | boolean | false | Dims the textarea and blocks interaction. |
| readOnly | boolean | false | Keeps content visible and copyable but not editable. |
| required | boolean | false | Native validation. Pair with the 'Required' label pattern in this doc. |
| aria-invalid | boolean | false | Signals error state. Combine with border-destructive on className. |
| className | string | — | Merged with base classes via cn(). Do not add shadow-* utilities. |
When to use
- Free-form text: rule summary, notes, description, feedback.
- JSON snippets or short code that isn't syntax-highlighted.
- Anywhere you expect more than one line of typed input.
When not to use
- Single-line query — use Input.
- Code with syntax highlighting — use a dedicated code editor primitive.
- Rich text (bold, links, lists) — use a rich text editor, not Textarea.
Usage
- Pair with a Label describing the expected content.
- Set a rows or min-h that matches the average length users will type.
- Show a character counter when there's a hard max length.
- Don't disable vertical resize — users expect it.
- Don't allow horizontal resize — it breaks the form grid.
- Don't add drop shadow — flat border only, same as Input.
- Don't use Textarea for a single-line search or query.
Related
Cross-links to atoms and patterns you may reach for next.