Noctis

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

Theme scope

ThemeScope re-tunes a whole region's shape — corner radius, density, and type scale — through the cascade, with no per-component prop and without touching the app-wide theme. Tweak the knobs below and watch every textarea inside the scope retune together. See Customization for nesting, reset, and the portal caveat.

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.