Components
Radio Group
A set of mutually exclusive options where exactly one item can be selected at a time. Built on Radix UI with full keyboard navigation and roving focus.
Basic usage
Pair each RadioGroupItem with a Label and set defaultValue for an uncontrolled group. The root is a grid gap-3, so stacked options need no extra layout classes.
Card selection
Wrap each item in a Label and match on has-[[data-state=checked]] to highlight the selected card. The item is a button, not a native input, so has-[:checked] would only ever match inside a form — Radix's data-state always matches. Use the brand role tokens for the highlight: border-primary-ink for the border and a bg-primary/10 tint for the fill.
Horizontal
orientation tells the roving-focus manager which arrow keys move between items — it does not change the layout. Add grid-flow-col (or your own flex classes) to place the options in a row.
Invalid
Set aria-invalid on the items after a failed validation. The border switches to border-destructive-ink — the ink step, which stays readable on the high-contrast palette where --destructive is a fill colour rather than a line colour.
Shipping speed
Pick a shipping speed to continue.
Disabled items
Add disabled to a single RadioGroupItem to skip it during keyboard navigation, or to the group to disable every option.
Props
RadioGroup
| Prop | Type | Default | Description |
|---|---|---|---|
defaultValue | string | — | The value of the item checked by default in an uncontrolled group. |
value | string | null | — | The controlled value of the checked item. Pair with onValueChange. |
onValueChange | (value: string) => void | — | Callback fired when the selected item changes. |
orientation | 'horizontal' | 'vertical' | — | Arrow-key direction for roving focus; both axes work when it is left unset. It does not lay the group out — the root is a grid, so pass className to change the visual direction. |
loop | boolean | true | Whether arrow keys wrap from the last item back to the first. |
dir | 'ltr' | 'rtl' | — | Reading direction used for horizontal arrow navigation. |
disabled | boolean | false | Disables every item in the group. |
required | boolean | false | Marks selection as required when used inside a form. |
name | string | — | The name submitted with the group in a native form. |
className | string | — | Merged onto the root, which defaults to grid gap-3. |
...rest | Radix RadioGroup.Root props | — | Everything else is forwarded to the root div. |
RadioGroupItem
| Prop | Type | Default | Description |
|---|---|---|---|
valuerequired | string | — | The unique value this item represents. |
disabled | boolean | false | Disables this individual item. |
required | boolean | false | Marks this item as required for native form validation. |
id | string | — | Element id, used to associate a Label via htmlFor. |
aria-invalid | boolean | — | Switches the border to border-destructive-ink and tints the ring — use it for failed validation. |
className | string | — | Additional Tailwind classes to merge. |
...rest | Radix RadioGroup.Item props | — | Everything else is forwarded to the underlying button. |
The package also exports the prop types RadioGroupProps and RadioGroupItemProps for wrappers built on top of these.
Accessibility
- Implements the WAI-ARIA radio group pattern with
role="radiogroup"and rovingtabindex— the group is a single Tab stop. - Arrow keys move between options and select them; Space selects the focused item, and disabled items are skipped.
- Associate each item with a
Labelvia matchingid/htmlForso the label is announced and becomes a click target. - A
focus-visiblering using the brand gold ring token marks the focused item during keyboard navigation. - The item has a transparent fill, so the selected dot is the only thing that carries the state — a non-text visual under WCAG 1.4.11. It is painted with
fill-control-on, notfill-primary: the raw brand gold scores 1.86:1 on a white page, while the high-contrast palette re-tunes--control-onto #B48700 in light (3.06–3.28:1 across the surfaces a radio can sit on) and to the light gold step in dark (10.80–12.67:1). - In dark mode the ring alone would be almost invisible against the card, so the item adds a
dark:bg-input-fillwash. That variable collapses to transparent on the high-contrast palette, where the border is already strong enough on its own.