Radio lets users pick a single option from a small, mutually exclusive set.
The root owns the group state (value, defaultValue, onChange) and cascades
visual props (size, position, invalid) to every item.
Switch the group to orientation="horizontal" for inline choices, opt into
spread to stretch items along the axis, or flip the indicator to the trailing
edge with position="right". Per-item position overrides the group default.
functionLayoutDemo(){return(<divclassName="grid gap-6"><Radioaria-label="Trip type"orientation="horizontal"spreaddefaultValue="round"><Radio.Itemvalue="one"><Radio.Indicator/><Radio.Label>One-way</Radio.Label></Radio.Item><Radio.Itemvalue="round"><Radio.Indicator/><Radio.Label>Round trip</Radio.Label></Radio.Item><Radio.Itemvalue="multi"><Radio.Indicator/><Radio.Label>Multi-city</Radio.Label></Radio.Item></Radio><Radioaria-label="Indicator on the trailing edge"position="right"defaultValue="email"><Radio.Itemvalue="email"><Radio.Indicator/><Radio.Label> Email<spanclassName="text-text-base text-(length:--desktop-body-xs-size) leading-(--desktop-body-xs-line-height) font-normal"> Booking confirmations and itinerary changes.</span></Radio.Label></Radio.Item><Radio.Itemvalue="sms"><Radio.Indicator/><Radio.Label> SMS<spanclassName="text-text-base text-(length:--desktop-body-xs-size) leading-(--desktop-body-xs-line-height) font-normal"> Gate changes and boarding reminders.</span></Radio.Label></Radio.Item></Radio></div>);}render(<LayoutDemo/>);
functionControlledRadioDemo(){const[value, setValue]=useState('return');return(<divclassName="grid gap-3"><BadgeclassName="w-fit"variant="neutral"appearance="outlined"> Selected: {value}</Badge><Radioaria-label="Refund preference"value={value}onChange={setValue}><Radio.Itemvalue="return"><Radio.Indicator/><Radio.Label> Refund to original payment<spanclassName="text-text-base text-(length:--desktop-body-xs-size) leading-(--desktop-body-xs-line-height) font-normal"> Settles in 5–10 business days.</span></Radio.Label></Radio.Item><Radio.Itemvalue="voucher"><Radio.Indicator/><Radio.Label> Travel voucher<spanclassName="text-text-base text-(length:--desktop-body-xs-size) leading-(--desktop-body-xs-line-height) font-normal"> Includes a 10% bonus, valid for 12 months.</span></Radio.Label></Radio.Item></Radio></div>);}render(<ControlledRadioDemo/>);
functionDisabledDemo(){return(<Radioaria-label="Ancillary services"defaultValue="lounge"><Radio.Itemvalue="lounge"><Radio.Indicator/><Radio.Label> Lounge access<spanclassName="text-text-base text-(length:--desktop-body-xs-size) leading-(--desktop-body-xs-line-height) font-normal"> Available at IST and select hubs.</span></Radio.Label></Radio.Item><Radio.Itemvalue="upgrade"disabled><Radio.Indicator/><Radio.Label> Upgrade to Business (sold out)<spanclassName="text-text-base text-(length:--desktop-body-xs-size) leading-(--desktop-body-xs-line-height) font-normal"> No seats available on this flight.</span></Radio.Label></Radio.Item></Radio>);}render(<DisabledDemo/>);
Radio.Item accepts a function child that receives the per-item state from Spar
(isChecked, disabled, isFocused, select), so each item can react to its
own selection without reading context manually.
functionRenderPropDemo(){return(<Radioaria-label="Seat row preference"defaultValue="exit"><Radio.Itemvalue="front">{({ isChecked })=>(<><Radio.Indicator/><Radio.Label> Front of cabin<spanclassName="text-text-base text-(length:--desktop-body-xs-size) leading-(--desktop-body-xs-line-height) font-normal">{isChecked ?'Selected — boards first.':'Quieter, near the galley.'}</span></Radio.Label></>)}</Radio.Item><Radio.Itemvalue="exit">{({ isChecked })=>(<><Radio.Indicator/><Radio.Label> Exit row<spanclassName="text-text-base text-(length:--desktop-body-xs-size) leading-(--desktop-body-xs-line-height) font-normal">{isChecked ?'Selected — extra legroom, eligibility required.':'Extra legroom, eligibility required.'}</span></Radio.Label></>)}</Radio.Item></Radio>);}render(<RenderPropDemo/>);
The root renders role="radiogroup"; each Radio.Item is a
<span role="radio"> wrapping a visually hidden <input type="radio"> so the
group participates in native form submission. (<label> allows no ARIA role,
so it is not the default — pass as for another element if you need one.)
Provide a group label via aria-label or aria-labelledby. Radio.Label
labels the individual item — compose helper text or richer markup directly
inside it.
Radio.Indicator is decorative (aria-hidden); the checked state is surfaced
to assistive tech via aria-checked on the item.
Key
Behavior
Tab
Move focus into the group (lands on the checked item, or the first).
↓ / →
Move focus to the next item and select it.
↑ / ←
Move focus to the previous item and select it.
Space / Enter
Select the focused item.
Disabled items skip both pointer and keyboard activation, and the first enabled
item becomes the tab stop when the checked one is disabled. Set selectOnFocus
to false if you want focus and selection to decouple, and autoFocus to move
focus into the group on mount. A readOnly group still moves focus with the
keyboard but never changes its value.
Consumer event handlers are composed with Spar's
A consumer onKeyDown / onFocus / onBlur on Radio, and onClick /
onKeyDown / onFocus on Radio.Item, run before the built-in handler and
do not replace it. Call event.preventDefault() inside your handler to veto the
built-in navigation or selection for that event.
Size scale. Cascades to descendant Radio.Items via context.
invalid
boolean
false
Marks the group as visually invalid. Cascades to descendant Radio.Items. When nested inside a <Field> with invalid, this is inherited automatically; a direct prop on <Radio> always wins. Maps to Spar's invalid for ARIA wiring (aria-invalid).
spread
boolean
false
Stretches each item to share the group axis equally. Layout intent only — styling is recipe-side via [data-spread] > [data-slot="root"].
Native keydown handler on the radiogroup root. Composed with Spar's roving keyboard handler: the consumer handler runs first, then Arrow / Home / End navigation (and selection when selectOnFocus is on) proceeds as usual. Call event.preventDefault() to veto the built-in navigation for that key.
Stable selector for wrapper styling on the root slot.
data-size
Always
Reflects the resolved size prop so theme recipes can scope size variants.
data-position
Always
Reflects the resolved indicator position (left / right).
data-invalid
When invalid is true, or inherited from a parent <Field invalid>.
Theme hook for the invalid state. Emitted by Spar on the radiogroup root only; items style themselves through the ancestor selector .tk-radio[data-invalid] .tk-radio-item.
data-spread
When spread is true.
Layout hook used by recipes to stretch each item along the group axis.
data-orientation
Always
Reflects the resolved orientation (vertical / horizontal). Emitted by Spar.
data-disabled
When disabled is true.
Theme hook for the disabled group. Emitted by Spar.
data-required
When required is true.
Theme hook surfacing the required state. Emitted by Spar.
Native click handler on the item. Composed with Spar's selection handler: the consumer handler runs first, then the item is selected. Call event.preventDefault() to veto the selection for that click.
onKeyDown
KeyboardEventHandler<HTMLElement>
-
Native keydown handler on the item. Composed with Spar's Space / Enter selection handler (consumer first); event.preventDefault() vetoes the selection. Arrow / Home / End navigation is handled on the Radio root.
onFocus
FocusEventHandler<HTMLElement>
-
Native focus handler on the item. Composed with Spar's focus tracking (roving tabindex, data-focus, and select-on-focus); the consumer handler runs first and event.preventDefault() vetoes the built-in behaviour.