DocsPlatform

Radio

One RadioGroup owns one value. The browser still owns native radio semantics, arrow-key movement, mutual exclusivity, the single tab stop and form submission.

Live machine

The shipped radioGroupMachine over the adapter fixture; the committed value lives in machine context, nowhere else.

radio-group · live via createService

States · walked from the machine

ready

Transitions from here · click to send

Context

{
  "mode": "uncontrolled",
  "value": "react"
}

Log · newest first

No events yet — use the component above or send a transition.

Layers

Machine (@grassroot/ui-machines/radio) is pure group state. Headless adds the same native input anatomy in React, Vue, Solid, Svelte and Angular. Vanilla (mountRadioGroup) is the sixth mount path. The five styled packages add the RadioGroup recipe and keep standalone Radio working.

Layer
Package
Responsibility
Machine
@grassroot/ui-machines/radio
Controlled/uncontrolled group value and guarded selection.
Headless
@grassroot/ui-headless-*
Scoped props, native inputs, form reset and relationships.
Vanilla
@grassroot/ui-headless-core/radio
Framework-free mount with the same contract.
Styled
@grassroot/ui-*
Tokens, classes and framework-native authoring.
Layer
Machine
Package
@grassroot/ui-machines/radio
Responsibility
Controlled/uncontrolled group value and guarded selection.
Layer
Headless
Package
@grassroot/ui-headless-*
Responsibility
Scoped props, native inputs, form reset and relationships.
Layer
Vanilla
Package
@grassroot/ui-headless-core/radio
Responsibility
Framework-free mount with the same contract.
Layer
Styled
Package
@grassroot/ui-*
Responsibility
Tokens, classes and framework-native authoring.

Behaviour contract

value/defaultValue is one context-backed binding. Uncontrolled selection commits and emits one VALUE_CHANGE_REQUEST; controlled selection proposes without local drift. Re-selecting the current value, or selecting while disabled/read-only, is a true no-op. RESET restores defaultValue only in uncontrolled mode.

Anatomy

RadioGroup.Root           role="radiogroup"
RadioGroup.Label          names the group
RadioGroup.Description    describes the group
RadioGroup.Item           native <label> per option
RadioGroup.ItemInput      native <input type="radio">
RadioGroup.ItemControl    visual box
RadioGroup.ItemIndicator  selected dot
RadioGroup.ItemLabel      option text

Every part carries data-scope="radio-group", a keyed data-part and the option's checked/unchecked state. The input is always the literal sibling immediately before its control so the existing peer-checked recipe remains valid.

Native behaviour

Each option is a real radio input with the shared group name, its value, and optional required. No custom roving-focus implementation is added. Form submission, validation, arrow keys and mutual exclusion stay browser-native. Controlled activation restores the native checked property immediately when the parent has not adopted the proposal.

Accessibility

Native radio inputs keep their native role and checked state: the contract emits no role="radio" and no aria-checked. A rendered Label and Description register scoped IDs for aria-labelledby and aria-describedby; a bare group uses ariaLabel. A development diagnostic reports an unnamed group without throwing.

Evidence