Skip to content

Textarea

A multi-line text field for longer freeform input.

Stablev0.4.2added in v0.1.0@garn/ui/textarea
On this page

Default

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.

terminal
npx garn-ui add textarea
First time? Set up garn in your project
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

SelectorState
:hoverhover
:focusfocus
:focus-visiblefocus-visible
[disabled]disabled
::placeholderplaceholder
PropertyTokenTier
minHeight--garn-control-h-mdsemantic · air
paddingInline--garn-control-px-mdsemantic · air
borderColor--garn-foregroundsemantic

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

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

warn

Give every textarea an accessible label (a <label htmlFor>, or aria-label/aria-labelledby).

when no associated <label htmlFor>, aria-label, or aria-labelledby

A1.3.1Info and RelationshipsAA2.4.7Focus VisibleA4.1.2Name, Role, Value
  • Associate a visible <label> (htmlFor) or provide aria-label / aria-labelledby.