Components
TextArea
The canonical multi-line text input — the block sibling of <Input>. Same friendly field treatment as Input — a solid surface with a visible border; gold glow on focus, red on invalid, green on valid, dimmed when disabled — grown to multiple lines. Vertically resizable by default; pass autoGrow to size it to its content instead.
TextArea is the primitive. For the form unit — label + helper / error with auto-linked ARIA — compose with TextAreaField. For a single line, reach for <Input> instead.
States
The same five treatments as Input, on a taller box. Idle is a clean border on a solid surface; focus lights up gold with a soft glow; invalid turns red, valid turns green (each pairs with an icon — ADR-0017); disabled dims to a muted fill. The sixth tile shows autoGrow — type to watch it size to its content.
In a form
Inside a form, reach for TextAreaField — the form-bound compound. It reads the field from TanStack Form context, maps touched + errors to the red border and an error HelperText, and wires the label / control / description ARIA. Same contract as TextField, multi-line.
Anatomy
"Materiality" — content on glass, hairline frames, lift — is the proposed system's concept, and the tokens below document that anatomy. The official field keeps it flat and friendly: a solid, opaque surface, a visible border, and color doing the state work — gold glow on focus, red on error, green on valid.
- body
- Transparent,
--space-pxhairline in--border-hairline,--radius-smcorners.That transparent, hairline-framed body is the proposed treatment. The official field is opaque — solid background, regular border, rounded corners. Min-height floors the box; rows sets the initial line count.
- state frames
- Focus
--gold-aa-500, invalid--rust-500, valid--patina-500; each with the matching focus ring.Color carries the state in both systems. Officially: gold for focus, red for errors, green for valid — each with a soft matching glow. State colors always pair with a HelperText icon, never color alone.
- resize
- Vertical by default;
autoGrowswitches to JS sizing (no handle, no scroll).Horizontal resize is off — width belongs to the layout, not the user.
- lift
--shadow-xsat rest,--shadow-smon focus.Lift is the proposed system's trick — official fields don't float. They sit flat on the page, and the focus glow does the talking.
Props
- invalid?: boolean
- Red border + aria-invalid. Wins over valid if both are set.
- valid?: boolean
- Green border + aria-invalid="false". Use after a positive validation passes.
- autoGrow?: boolean
- Grow to fit content instead of scrolling; disables the manual resize handle.
- rows, placeholder, value, onChange, disabled, maxLength, …
- Every native
<textarea>attribute passes through.rowssets the initial height (default 4). - ref: Ref<HTMLTextAreaElement>
- Forwarded to the underlying textarea (autoGrow reads scrollHeight from it).
Usage
import { TextArea } from '@halo-compliance/ui'
<TextArea
value={notes}
onChange={(e) => setNotes(e.target.value)}
rows={4}
placeholder="Add a note…"
/><TextArea value={notes} onChange={onChange} autoGrow rows={2} /><form.AppField name="notes">
{() => (
<TextAreaField
label="Describe the issue"
helperText="What happened, and what you expected."
autoGrow
/>
)}
</form.AppField>When to use
- Free-form prose — a note, a description, a reason, a comment that may run several lines.
- Unknown-length input — pair with autoGrow so the field follows the content instead of trapping it in a scroller.
- A captured artifact — pasted logs, a rulebook excerpt, a block someone needs to read back.
Antipatterns
- A single line. If it is one line — a name, an email, a number — that is Input. A one-row TextArea is a misshapen Input.
- Rich text. Bold / links / lists need an editor, not a textarea. TextArea is plain text only.
- A bare control. Always give it a label — compose TextAreaField, or wire your own <label> + aria-describedby.
Related
- /input — the single-line sibling; same look, with icon slots.
- /text-field — the single-line form binding (TextArea’s TextAreaField mirrors it).
- /form — the form layer TextAreaField plugs into.
- /tokens — the
--shadow-xs/--radius-sm/ focus-ring tokens behind the field.