Radio Group
A radio group is a <fieldset role="radiogroup"> on top of Headless UI's
RadioGroup. Its label is the <legend>. Give it an options array for
the common case, or compose RadioGroup.Option children when a row needs
custom content.
import RadioGroup from '@zuilib/components/radio-group'
Card variant
The default. Each option is a bordered row that is clickable in full; the
selected card takes a --primary border and tint. Row padding derives from
--control-height-*, so a single-line card is exactly one control tall.
List variant and orientation
variant="list" renders plain radios with the label beside them.
orientation="horizontal" lays options in a wrapping row and sets
aria-orientation.
Sizes
Composed options
RadioGroup.Option with children replaces the default row: compose
RadioGroup.Indicator and your own content. Group label /
description can also be RadioGroup.Label / RadioGroup.Description
children.
In a FormItem
Inside a FormItem the group adopts the item's control id and appends the
item's Label, Description and Message ids to aria-labelledby /
aria-describedby. invalid and disabled default from the item.
Props
RadioGroupProps<T> extends the <fieldset> attributes with:
| Prop | Type | Default | Description |
|---|---|---|---|
value | T | — | Controlled value |
defaultValue | T | — | Initial value for uncontrolled usage |
onChange | (value: T) => void | — | |
by | keyof T | (a: T, b: T) => boolean | reference equality | How option values are compared to the current value (object values) |
options | Array<{value: T; label: ReactNode; description?: ReactNode; icon?: ReactNode; disabled?: boolean}> | — | Renders one RadioGroup.Option per entry |
children | ReactNode | — | Composed RadioGroup.Option / Label / Description |
label | ReactNode | — | Group label, rendered as the <legend> and wired to aria-labelledby |
description | ReactNode | — | Group help text, wired to aria-describedby |
variant | 'card' | 'list' | 'card' | Bordered clickable rows, or plain radios |
orientation | 'vertical' | 'horizontal' | 'vertical' | Layout; also aria-orientation and data-orientation |
size | 'sm' | 'md' | 'lg' | 'md' | |
invalid | boolean | FormItem invalid | aria-invalid on the group and every radio, destructive borders |
disabled | boolean | Field / FormItem / Fieldset disabled | |
name | string | — | Native form field name; hidden inputs are rendered for submission |
form | string | — | Id of the <form> to submit with when rendered outside it |
id | string | FormItem control id | Replaces the id adopted from an enclosing FormItem |
className | string | — | Merged last onto the fieldset |
RadioGroup.Option (RadioGroupOptionProps<T>, extends <div> attributes):
| Prop | Type | Default | Description |
|---|---|---|---|
valuerequired | T | — | |
label | ReactNode | — | |
description | ReactNode | — | |
icon | ReactNode | — | Between the indicator and the text; sized per size, --primary when checked |
disabled | boolean | false | |
autoFocus | boolean | false | |
children | ReactNode | — | Replaces the default row (indicator, icon, label, description) |
className | string | — | Merged last onto the radio element |
Statics and named exports: RadioGroup.Option, RadioGroup.Indicator
(span attributes), RadioGroup.Label (legend attributes),
RadioGroup.Description (paragraph attributes).
Slots
| Slot | Element | Notes |
|---|---|---|
[data-slot="radio-group"] | fieldset | Root, role="radiogroup". Also data-variant, data-orientation, data-size, data-invalid, data-disabled |
[data-slot="radio-group-label"] | legend | — |
[data-slot="radio-group-description"] | p | — |
[data-slot="radio-group-option-field"] | div | display: contents Headless UI Field around each option |
[data-slot="radio-group-option"] | div | The radio row, role="radio". Headless UI data-checked, data-focus, data-hover, data-disabled; plus data-invalid |
[data-slot="radio-group-indicator"] | span | The circle; aria-hidden |
[data-slot="radio-group-indicator-dot"] | span | The inner dot, scaled in when checked |
[data-slot="radio-group-option-icon"] | span | — |
[data-slot="radio-group-option-content"] | span | Wraps label and description |
[data-slot="radio-group-option-label"] | label | — |
[data-slot="radio-group-option-description"] | p | — |
/* Consumer CSS: square cards */
[data-slot="radio-group"][data-variant="card"] [data-slot="radio-group-option"] {
border-radius: 0;
}
Tokens
| Token | Used for |
|---|---|
--input, --background | Card and indicator borders and fills |
--primary | Checked border, tint, dot and icon |
--ring | Hover border and focus ring |
--destructive | Invalid borders and ring |
--control-height-*, --control-padding-x-* | Card row padding per size |
--spacing | Gaps and indicator sizes |
--radius-lg | Card corners |
--duration-fast | Colour and dot transitions |
Accessibility
- A
<fieldset role="radiogroup">with the label as its<legend>; the legend id is inaria-labelledby, the description id inaria-describedby. - Each option is
role="radio"witharia-checked, named by its label and described by its description through a per-option Headless UIField. - Keyboard: Tab moves to the checked option (or the first enabled one); Arrow keys move selection between options; Space checks the focused option.
- Focus ring:
data-focuson the card, or on the indicator in the list variant; destructive while invalid. invalidsetsaria-invalidon the group and on every radio.disabledis inherited from an enclosingField/FormItem/Fieldset.namerenders hidden inputs so the value submits with a native<form>.