- Layer
- Machine
- Package
@grassroot/ui-machines/radio- Responsibility
- Controlled/uncontrolled group value and guarded selection.
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
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.
@grassroot/ui-machines/radio@grassroot/ui-headless-*@grassroot/ui-headless-core/radio@grassroot/ui-*- 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
- Radio in the component catalogue
radio.spec.md, the reviewable contract.- Checkbox and Radio baseline, covering C2-C4 and inherited Switch decisions.