Components

Textarea

Multi-line text input matching Input's border and focus. Resizes vertically only. Minimum height 64px (min-h-16), rounded-md, no shadow.

Updated Jul 16, 2026 by Leonardo Posada

Anatomy

Explain in one sentence what this rule does.

  1. 1
    Label

    Describes what the user should type. Never hidden — always above the textarea.

  2. 2
    Textarea

    The multi-line field. Same flat border-input and focus ring as Input.

  3. 3
    Helper 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.

Default
<Textarea rows={3} placeholder="Explain what this rule does…" />
Disabled
<Textarea rows={3} disabled placeholder="Managed by admin" />
Read only
<Textarea rows={3} readOnly value="…" />
Error

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.

0 / 140

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.

Optional
Optional
<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>
Required
Required
<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>
Vertical resize only
Yuno textareas keep the browser's vertical resize handle. Never set resize-none — users need to expand for long content. Never allow horizontal resize — it breaks form grids.

Recipes

Ready-to-copy compositions covering the most common Yuno usages of this atom.

Rule summary with counter

Textarea + live character counter for a short business explanation with a max length.

0 / 140
<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>
Feedback with helper

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>
Read-only statement

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.

Base import + Label for the form-field pattern.
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.

PropTypeDefaultDescription
rowsnumber2Sets the visible line count. Common presets: 2 / 4 / 8.
value / defaultValuestringControlled or uncontrolled value. Controlled needs onChange.
placeholderstringHint text shown when the textarea is empty.
disabledbooleanfalseDims the textarea and blocks interaction.
readOnlybooleanfalseKeeps content visible and copyable but not editable.
requiredbooleanfalseNative validation. Pair with the 'Required' label pattern in this doc.
aria-invalidbooleanfalseSignals error state. Combine with border-destructive on className.
classNamestringMerged 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

Do
  • 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
  • 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.

  • InputFor single-line entries.
  • FormTo pair Textarea with react-hook-form + zod.