Textarea
A multi-line text field for longer freeform input.
@garn/ui/textareaDefault
Default textarea.
Appearance
The shared field-surface fill axis (see lib/field-variants.ts; identical to Input): outline = neutral border on transparent fill; soft = subtle fill that clears to the outline on focus; ghost = seamless until focus.
Outline
Soft
Ghost
Sizing
Sets padding + a min-height of ~2 control rows; tracks the same grid as Input.
Spacing
The same control rendered across garn's density modes — compact, default, and spacious.
States
Interactive states, toggled live.
Labeled
Textarea with a label.
Installation
garn is copy-in — the garn CLI writes this component's source into your project, so you own and can edit it. It resolves any base files and installs the npm dependencies below for you.
npx garn-ui add textarea- Registry
textarea- Deps
class-variance-authority- Registry deps
utilsfield-variantsuse-resize-observeruse-isomorphic-layout-effect
Import
import { Textarea } from "@garn/ui/textarea";Props
The component's public props and their types.
appearance- Description
- No description.
- Type
"ghost" | "outline" | "soft"- Default
outline
size- Description
- No description.
- Type
"lg" | "md" | "sm" | "xl" | "xs"- Default
md
autoResize- Description
- Grow to fit content instead of scrolling — re-measured on input and on width changes (wrapping shifts height). The `size` min-height stays the floor; set a `max-height` class to cap it (it starts scrolling past the cap).
- Type
boolean- Default
false
Plus 294 inherited native <textarea> attributes.
Styling
Target these data-slots and states, and remap these tokens, to restyle without forking the component.
[data-slot="textarea"]States
| Selector | State | Description |
|---|---|---|
:hover | hover | Edge / fill grows slightly stronger (shared field surface). |
:focus | focus | Border grows less transparent (foreground/40) on any focus; soft/ghost clear their fill. |
:focus-visible | focus-visible | Keyboard focus adds a 2px neutral ring (foreground/15). |
[disabled] | disabled | Dims and blocks typing. |
::placeholder | placeholder | Muted placeholder text. |
| Property | Token | Tier |
|---|---|---|
minHeight | --garn-control-h-md | semantic · air |
paddingInline | --garn-control-px-md | semantic · air |
borderColor | --garn-foreground | semantic |
When to use
Collect multi-line freeform text — comments, descriptions, messages, notes.
Reach for something else when
- A single line (use input)
- choosing among fixed options (use select or radio-group).
Overview
Textarea collects multi-line freeform text — a comment, a description, a message. It renders the same field surface as Input (appearance × size), so it sits on the shared grid and reddens on aria-invalid like every other control; its minimum height is derived as two control rows so it reads as multi-line at rest. For a single line, use Input; for a fixed-length code, InputOTP; for a choice, Select.
Guidelines
Always pair a Textarea with a Label, and size it to the expected input. Start tall enough to signal that a few sentences are welcome; a single-row box invites single-word answers. Use the placeholder for an example, never as the field's name.
Show limits before they're hit, not after. When there's a max length, surface a live character count and warn as it approaches the cap — don't silently truncate on submit. Wire validation through aria-invalid + a described error message so the state is announced.
Let users grow the field. Allow vertical resize (or auto-grow) for longer entries rather than trapping a long message in a small scroll box. Don't disable the browser's resize handle without giving an equivalent way to see more.
Best practices
- Pair with a label; allow vertical resize unless the layout requires a fixed height.
- Set a sensible number of rows / min-height for the expected content.
- Don't use a textarea for one-line values.
- Don't disable resizing without a layout reason.
Content guidelines
- Use the placeholder for a brief example of the expected input, never as the label.
- If there's a length limit, state it near the field and show the remaining count.
- Keep helper text to one line of guidance — not instructions on how to type.
Troubleshooting
Typing does nothing / React warns about a controlled component.
Cause. Passing value without onChange.
Fix. Use defaultValue for uncontrolled, or pass both value and onChange.
Accessibility
- Role
textbox- Focus
- Border darkens (foreground/40) on any focus; keyboard focus adds a 2px neutral ring (foreground/15).
Accessibility requirements
Give every textarea an accessible label (a <label htmlFor>, or aria-label/aria-labelledby).
when no associated <label htmlFor>, aria-label, or aria-labelledby
- Associate a visible <label> (htmlFor) or provide aria-label / aria-labelledby.