Noctis

Input

The text field — an editable control wearing the field surface, border, and a calm, ring-less focus treatment. The canonical Noctis field: validity and disabled flow from the control, with leading and trailing adornments, interactive in-field actions, a live character count, segmented addons, two sizes, and read-only.

Basic

Compose Input.Root — the shell that paints the field chrome and owns the size — around an Input.Control, the Base UI input. Focusing the control lights the whole shell; the border shifts to the accent focus role, no surrounding ring. A subtle rest shadow gives the field its seated, slightly-inset feel.

With a field

The canonical usage — and the pattern the whole field family copies. Wrap the input in a Noctis Field.Root with a Field.Label, Field.Description, and Field.Error: the label auto-associates, the description and error join the control through aria-describedby, and aria-invalid flips from validation — all without setting a single state prop on the shell.

We only use it to sign you in.

Sizes

Two heights — medium (the default) and large — sharing one field surface and type rhythm, both riding the density knob. The size lives on Input.Root; the control, adornments, actions, and in-field glyphs inherit its metrics.

Adornments

Flank the control with a leading or trailing adornment — a search glyph, a currency mark, a unit. Set side to start or end; the shell's row orders them logically, so they mirror under RTL, and their icons take the field's size automatically.

$USD

Addon groups

Set an adornment's variant to segment for a bordered cell flush to the field edge — the prefix/suffix idiom (https:// ▸ field ▸ .com). Pair the control with a trailing Input.Action for an inline button group.

https://.com

Clearable

Input.Action is an interactive in-field button with the system's ghost-control discipline — a neutral hover, its own focus ring, and a compact icon-button footprint tucked against the field edge. Give a clear button data-reveal="filled" and it appears only once the field holds a value, driven purely by CSS. Pressing Escape on a search-style field clears it too.

Password reveal

A reveal toggle is an Input.Action that keeps a static accessible name and flips aria-pressed (not its label), swapping the eye glyph and the control's type. Screen readers hear the pressed state change rather than a name shifting under them.

A search-typed field with a leading glyph and a trailing shortcut chip that cross-fades from ⌘K to Esc once the field is dirty. Escape clears the value and keeps focus.

CommandkEscape

Character count

Input.Count is a quiet trailing readout of the value length against a max. It escalates from muted to the warning role as the value nears the limit, then the danger role past it, and announces the remaining count through a polite, atomic live region — empty on first paint, so typing isn't narrated character by character. 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 and holds it even when focused. There is no invalid prop to mirror on the shell — one source of truth. The shell does keep an invalid escape hatch for shell-only styling without a control state.

Enter a valid email address.

Read-only

A read-only field reads as quiet-but-present: a calm border, no accent focus fill, 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, with no need to set disabled on the shell too.

File

A file input takes a compact, quiet selector button matched to the field, with the chosen filename in the muted role beside it.

Scoped radius

Noctis fields are pill by default — the shell rounds fully — but RadiusScope is the escape hatch: wrap a region and every primitive inside re-rounds (square it for a dense dashboard, soften it for a settings panel). The border, rest shadow, and segmented addons all follow the shell's radius, so the whole field rounds together.

Accessibility

  • State flows from the control. The shell reads the control's :disabled, aria-invalid, and readonly (and Base UI/Field data-*) through :has(), so validity and disabled have one source of truth — no double-setting invalid + aria-invalid or disabled on both.
  • 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. Validate on blur; write placeholders as example values, not instructions.
  • Calm, visible focus. Focus shifts the border to the accent focus role (no surrounding ring) — clearly visible without the aggressive glow, on both keyboard and pointer focus the way a text field naturally highlights when active.
  • Actions are real, labelled buttons. Input.Action renders a <button type="button">; an icon-only action needs an aria-label (or a VisuallyHidden child). The reveal toggle keeps a static name and flips aria-pressed. Tab reaches the control before trailing actions (DOM order).
  • The count announces politely. Input.Count exposes the remaining characters through an aria-live="polite", aria-atomic region that starts empty, and joins the control via aria-describedby.
  • Logical & RTL-correct. All geometry is logical (padding-inline, gap, logical borders and insets), so adornments, actions, segments, and the count all mirror under RTL by construction.

Anatomy

  • Input.Root — the 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.
  • Input.Control — the editable Base UI input. Stays unstyled so the value, caret, and selection inherit the field's type; its disabled/readOnly/aria-invalid drive the shell.
  • Input.Adornment — a leading (start) or trailing (end) icon, unit, or affordance. The default quiet variant is transparent; segment is a bordered cell flush to the field edge.
  • Input.Action — an interactive in-field button (clear, reveal, shortcut) with ghost-control discipline, sized to the field height. Add data-reveal="filled" to show it only while the field holds a value.
  • Input.Count — a live character-count readout that escalates muted → warning → danger near and past the limit.

The parts stamp the data-slot values noctis-input, noctis-input-control, noctis-input-adornment, noctis-input-action, and noctis-input-count. Target them with the state attributes — data-size on the shell, data-side/data-variant on an adornment, the control's data-filled/data-dirty/data-invalid/data-readonly — to style, say, a clear button that only appears once there's a value.

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, so a variable name or its resolution default can't drift. The minted tokens are the public override seam: set one on any ancestor and every input in that region retunes — e.g. .compact { --noctis-input-height: 2rem; } shortens every field beneath it. See Customization for the full override ladder and Tokens for the whole graph.

Token

API reference

Generated from the component's types — every prop, type, default, and description comes straight from the source. Input.Control extends the Base UI Input props, so onValueChange, defaultValue, and the native input attributes pass straight through; expand a row for the full type and description.

Input.Root

Prop

Input.Control

Prop

Input.Adornment

Prop

Input.Action

Prop

Input.Count

Prop

AttributeDescription
data-slotThe rendered element of a given part (root shell, control, or adornment).
data-sizeThe size scale — `sm` | `md` | `lg`; the generated layer keys the per-size internals off it.
data-sideWhich edge an adornment sits on — `start` (leading) | `end` (trailing).
data-variantHow an adornment paints — `quiet` (default) | `segment` (a bordered cell flush to the field edge).
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 mutes the border and skips the focus fill.
data-filledPresent (on the control) when the field holds a value — gates the clear action's visibility.
data-dirtyPresent (on the control) when the value has changed from its initial — swaps the ⌘K chip to Esc.