Skip to main content

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.

Loading example

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.

Loading example

Sizes

Loading example

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.

Loading example

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.

Loading example

Props

RadioGroupProps<T> extends the <fieldset> attributes with:

PropTypeDefaultDescription
valueTControlled value
defaultValueTInitial value for uncontrolled usage
onChange(value: T) => void
bykeyof T | (a: T, b: T) => booleanreference equalityHow option values are compared to the current value (object values)
optionsArray<{value: T; label: ReactNode; description?: ReactNode; icon?: ReactNode; disabled?: boolean}>Renders one RadioGroup.Option per entry
childrenReactNodeComposed RadioGroup.Option / Label / Description
labelReactNodeGroup label, rendered as the <legend> and wired to aria-labelledby
descriptionReactNodeGroup 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'
invalidbooleanFormItem invalidaria-invalid on the group and every radio, destructive borders
disabledbooleanField / FormItem / Fieldset disabled
namestringNative form field name; hidden inputs are rendered for submission
formstringId of the <form> to submit with when rendered outside it
idstringFormItem control idReplaces the id adopted from an enclosing FormItem
classNamestringMerged last onto the fieldset

RadioGroup.Option (RadioGroupOptionProps<T>, extends <div> attributes):

PropTypeDefaultDescription
valuerequiredT
labelReactNode
descriptionReactNode
iconReactNodeBetween the indicator and the text; sized per size, --primary when checked
disabledbooleanfalse
autoFocusbooleanfalse
childrenReactNodeReplaces the default row (indicator, icon, label, description)
classNamestringMerged last onto the radio element

Statics and named exports: RadioGroup.Option, RadioGroup.Indicator (span attributes), RadioGroup.Label (legend attributes), RadioGroup.Description (paragraph attributes).

Slots

SlotElementNotes
[data-slot="radio-group"]fieldsetRoot, 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"]divdisplay: contents Headless UI Field around each option
[data-slot="radio-group-option"]divThe radio row, role="radio". Headless UI data-checked, data-focus, data-hover, data-disabled; plus data-invalid
[data-slot="radio-group-indicator"]spanThe circle; aria-hidden
[data-slot="radio-group-indicator-dot"]spanThe inner dot, scaled in when checked
[data-slot="radio-group-option-icon"]span
[data-slot="radio-group-option-content"]spanWraps 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

TokenUsed for
--input, --backgroundCard and indicator borders and fills
--primaryChecked border, tint, dot and icon
--ringHover border and focus ring
--destructiveInvalid borders and ring
--control-height-*, --control-padding-x-*Card row padding per size
--spacingGaps and indicator sizes
--radius-lgCard corners
--duration-fastColour and dot transitions

Accessibility

  • A <fieldset role="radiogroup"> with the label as its <legend>; the legend id is in aria-labelledby, the description id in aria-describedby.
  • Each option is role="radio" with aria-checked, named by its label and described by its description through a per-option Headless UI Field.
  • 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-focus on the card, or on the indicator in the list variant; destructive while invalid.
  • invalid sets aria-invalid on the group and on every radio. disabled is inherited from an enclosing Field / FormItem / Fieldset.
  • name renders hidden inputs so the value submits with a native <form>.