Noctis

Textarea

The multi-line text field — the block sibling of Input. It shares the same field surface, calm ring-less focus, rest shadow, and control-driven state, auto-grows with its content between a min and max height, keeps a vertical resize handle, and carries an optional toolbar footer for the shared in-field actions and a live character count.

Basic

Compose Textarea.Root — the block shell that paints the field chrome and owns the size — around a Textarea.Control, the editable textarea. It top-aligns its text and grows with content up to a cap, then scrolls; a vertical resize handle stays available.

With a field

Wrap the textarea in a Noctis Field.Root with a Field.Label, Field.Description, and Field.Error — the same framing Input uses. The label auto-associates, the description and error join the control through aria-describedby, and aria-invalid flips from validation, all without setting a state prop on the shell.

Shown on your public profile.

Sizes

Two sizes — medium (the default) and large — sharing one field surface and type rhythm, riding the density knob. The size lives on Textarea.Root; the control and toolbar inherit its metrics.

Composer

Textarea.Toolbar is a footer row, divided from the text by a hairline, for the shared Textarea.Actions and a Textarea.Count — the comment-composer pattern. The action and count are the exact same field affordances Input exposes (Input.Action/Input.Count), so the footer reads consistently across the family.

Character count

Textarea.Count is the shared character-count readout: it shows length / max, escalates mutedwarningdanger as the value nears and passes the limit, and announces the remaining count through a polite, atomic live region. Point the control's aria-describedby at it to join the count to the field.

Invalid

Validity flows from the control: set aria-invalid (or let a Noctis Field.Root set it from validation) and the shell draws the danger border, holding it even when focused. There is no invalid prop to mirror on the shell — one source of truth — though the shell keeps an invalid escape hatch for shell-only styling.

Please write at least a sentence.

Read-only

A read-only field reads as quiet-but-present: a calm border, no resize handle, and selectable, focusable text — distinct from the dimmed, not-allowed disabled field beside it. It flows from the control's readOnly.

Disabled

A disabled field dims to the disabled opacity and blocks the text cursor. Disable the control alone — the shell reads it through :has() and dims to match.

Accessibility

  • State flows from the control. The shell reads the control's :disabled, aria-invalid, and readonly (and Base UI/Field data-*) through :has() — one source of truth, no double-setting.
  • Pair it with a Field. A Noctis Field.Root auto-associates the label, joins the description and error through aria-describedby, and sets aria-invalid from validation.
  • Calm, visible focus. Focus shifts the border to the accent focus role (no surrounding ring), on both keyboard and pointer focus the way a text field naturally highlights when active.
  • Toolbar actions are real, labelled buttons. Textarea.Action renders a <button type="button">; an icon-only action needs an aria-label. They follow the control in DOM order.
  • The count announces politely. Textarea.Count exposes the remaining characters through an aria-live="polite", aria-atomic region that starts empty, joined to the control via aria-describedby.
  • Auto-grow is progressive. The field grows with content via CSS field-sizing where supported, with a small script fallback elsewhere; the vertical resize handle is always available.

Anatomy

  • Textarea.Root — the block field shell. Paints the surface, border, rest shadow, and the focus border; owns the size. Reads the control's state through :has(); keeps invalid/disabled as additive shell-only overrides. Clicking its padding focuses the control.
  • Textarea.Control — the editable textarea (Base UI's input rendered as a <textarea>). Auto-grows between the field's min and max height and keeps a resize handle; its disabled/readOnly/aria-invalid drive the shell.
  • Textarea.Toolbar — the optional footer row, divided from the text, hosting actions and a count.
  • Textarea.Action / Textarea.Count — the shared in-field affordances (the same parts as Input.Action/Input.Count), stamping noctis-field-action / noctis-field-count.

The shell parts stamp the data-slot values noctis-textarea, noctis-textarea-control, and noctis-textarea-toolbar; the toolbar hosts the shared noctis-field-action and noctis-field-count.

On surfaces

The same control re-tuned across the elevation scopes — the root canvas, an elevated panel, a menu, and a sunken well. It stays legible on every layer.

root
elevated
menu
sunken

Design tokens

Generated from the component's declaration — the same graph that mints the CSS. The minted tokens are the public override seam: set one on any ancestor and every textarea in that region retunes — e.g. .compact { --noctis-textarea-min-block-size: 3rem; }. See Customization for the full override ladder.

Token

API reference

Generated from the component's types — every prop, type, default, and description comes straight from the source. Textarea.Control extends the Base UI input props (rendered as a textarea), so onValueChange, defaultValue, rows, and the native textarea attributes pass straight through.

Textarea.Root

Prop

Textarea.Control

Prop

Textarea.Toolbar

Prop

Textarea.Action

Prop

Textarea.Count

Prop

AttributeDescription
data-slotThe rendered element of a given part (root shell, control, or toolbar).
data-sizeThe size scale — `sm` | `md` | `lg`; the generated layer keys the per-size internals off it.
data-invalidPresent when the field is in an invalid state — set by Base UI/Field on the control, or by the root override.
data-disabledPresent when the field is disabled — set by Base UI on the control, or by the root override.
data-readonlyPresent when the field is read-only — set by Base UI on the control; the shell holds the rest border.