Noctis

Radio

A single-select control. The user picks exactly one option with the pointer or the keyboard; the selected radio carries an accent border and a dot that scales in, on a true-circle box that stays round at any radius setting. A real form control — it forwards name / form / required / readOnly and can flag an invalid choice.

Basic

Compose Radio.Group (which owns the value) with one Radio.Field per option — each pairing a Radio box with its Radio.Label, the same first-class label idiom as Checkbox. The field wires the label to the box (clicking the label selects it, the label is the accessible name) and holds the box↔label gap; one size on the group sizes every row. Name the group with aria-label (or aria-labelledby), and drive it with value / onValueChange or an uncontrolled defaultValue.

Orientation

A group stacks vertically by default; orientation="horizontal" lays it out in a wrapping row. Arrow keys navigate both axes, and the row mirrors under RTL by construction.

Sizes

size on the group sets the box edge length of every radio — sm for dense rows, md (the default), or lg for cards and touch. The accent dot re-points with it, so it stays proportional.

Descriptions

Add a Radio.Description per option for helper text, wired to its radio with aria-describedby. Keep the description a sibling of the Radio.Field so it isn't folded into the accessible name.

1 project, community support.
Unlimited projects and history.
Shared workspaces and roles.

Validation

Mark the group required and flag it invalid to surface a validation error: the danger border paints on every radio and the error message is wired through aria-describedby. Validation fires at submit, not on blur — colour is never the only signal.

Cards

Radio.Card is a selectable card: a sharp bordered target wrapping a radio, so the whole card toggles. Selecting it tints the edge to the accent; hover is a neutral surface shift and the focus ring moves to the card. The control sits at the leading edge by default, or the trailing edge via controlPosition.

Disabled

Set disabled on a single Radio to lock one option inside an otherwise enabled group — arrow navigation skips it. Set disabled on the whole Radio.Group to dim and lock every option at once.

Standalone

A radio can be named by aria-label instead of visible text — an icon-style picker where the option is self-evident. It is still a group, so arrows and single-select apply.

Keyboard

KeyAction
TabMove focus into the group, landing on the selected radio (or the first); Tab again leaves the group.
/ / / Move to the next / previous radio and select it (selection follows focus), wrapping at the ends. Arrows mirror under RTL.
SpaceSelect the focused radio.

A disabled option inside an enabled group is skipped by the arrows. A radio group is a single composite tab stop — Tab advances to the next field, not the next radio.

Accessibility

  • Roles. Radio.Group is a radiogroup (named by aria-label / aria-labelledby / a <legend>); each Radio is a radio, named by its Radio.Label (or an aria-label for a label-less radio). aria-orientation reflects the orientation prop.
  • Validation. invalid sets aria-invalid on the group; pair it with an error message wired by aria-describedby. The danger border is a reinforcement, never the only signal.
  • Read-only. A read-only radio stays focusable and announced — reflected visually but never removed from the accessibility tree.
  • Descriptions. Radio.Description is associated to its radio with aria-describedby (the consumer sets the matching id).
  • Motion. The dot scales / fades in on select; under prefers-reduced-motion: reduce it appears instantly, meaning preserved.

Anatomy

Compose the control from its parts. Radio.Group is a Base UI RadioGroup, so selection, roving focus, arrow-key navigation, and the form surface (name / form / required / readOnly) come for free.

  • Radio.Group — the group; owns the selected value (value / onValueChange, or uncontrolled defaultValue), the shared size, and the orientation. Forwards name / form / required / readOnly to Base UI; flag the value in error with invalid; disable everything with disabled.
  • Radio — one radio box. Pass its value (and optional readOnly / required); it renders the accent dot (Radio.Indicator) for itself when selected.
  • Radio.Field — the labelled row pairing a box with its label (Base UI's Field.Root), mirroring Checkbox.Field. Inherits the group's size unless it sets its own, and cascades it to the box.
  • Radio.Label — the box's visible accessible name (Base UI's Field.Label); clicking it selects the box, and it mirrors the field's disabled / invalid state. For a label-less radio (an icon picker), give the Radio an aria-label instead.
  • Radio.Indicator — the accent dot; kept mounted so it scales in on select. Radio renders it automatically.
  • Radio.Description — muted helper text for a radio, wired by aria-describedby.
  • Radio.Card — a selectable card target wrapping a radio, its label, and an optional description.

Every rendered part carries a data-slot (noctis-radio-group, noctis-radio, noctis-radio-indicator, noctis-radio-field, noctis-radio-label, noctis-radio-description, noctis-radio-card) for host-side styling — pair it with the Base UI radio state attributes (data-checked, data-unchecked, data-disabled, data-readonly, data-invalid) and the data-size / data-orientation axes.

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 radio in that region retunes — e.g. .dense { --noctis-radio-group-gap: 0.375rem; } tightens the stack beneath it. Knobs that aren't minted are reached through the part's data-slot. 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. Each part gets its own table; the Group forwards the Base UI RadioGroup props it owns selection through. Expand a row for the full type and description.

Radio.Group

Prop

Radio.Root

Prop

Radio.Indicator

Prop

Radio.Description

Prop

Radio.Card

Prop

AttributeDescription
data-slotThe styling/testing hook: the part's stable slot name.
data-sizeSize axis on the radio box — `sm` | `md` | `lg`; re-points the box and dot edges.
data-orientationLayout axis on the group — `vertical` | `horizontal`; switches the row/column flow.
data-checkedPresent on the selected radio box (and its indicator).
data-uncheckedPresent on an unselected radio box.
data-disabledPresent on a disabled radio or group.
data-readonlyPresent on a read-only radio — valid and announced, but not editable.
data-invalidPresent on an invalid radio (and on a group flagged `invalid`); also honours `aria-invalid`.
data-controlOn a `Radio.Card`, where the control sits — `leading` | `trailing`.
data-control-hiddenOn a `Radio.Card` with `hideControl`, marks the dot hidden so the accent edge is the only signal.