Radio Group
A set of mutually exclusive radio options. Built on Radix UI.
Options
Disabled
Card variant
variant="card" draws each item as a card with a leading icon, a title and a one-line description. The chosen card is a lifted fill with a light inset edge and a check mark, so selection is never carried by colour alone. A disabled item stays in place and stays readable, is skipped by the arrow keys, and says why when you pass aria-describedby.
Public needs a verified booking.
| Item prop | Purpose |
|---|---|
icon | Leading icon or swatch; decorative and hidden from assistive tech |
title | The option's name; it is the item's accessible name |
description | One line saying what the option means; it describes the item |
aria-describedby | An id saying why an item is disabled; announced after the description |
The resting edge reads the shared --control-edge-* tokens, so --control-edge-width: 0 turns it off; focus and invalid keep drawing at --control-state-edge-width. Per-component overrides: --radio-card-bg, --radio-card-selected-bg, --radio-card-selected-border, --radio-card-border, --radio-card-radius, --radio-card-icon-size.
Installation
npx visor add radio-groupThis copies two files into your project:
components/ui/radio-group/radio-group.tsx— the componentcomponents/ui/radio-group/radio-group.module.css— the styles
Usage
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group/radio-group';
import { Label } from '@/components/ui/label/label';
<RadioGroup defaultValue="a">
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
<RadioGroupItem value="a" id="a" />
<Label htmlFor="a">Option A</Label>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
<RadioGroupItem value="b" id="b" />
<Label htmlFor="b">Option B</Label>
</div>
</RadioGroup>API Reference
RadioGroupProps
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | Controlled value of the selected radio item. |
defaultValue | string | — | Default selected value for uncontrolled usage. |
onValueChange | (value: string) => void | — | Callback when the selected value changes. |
disabled | boolean | false | Disables all radio items in the group. |
Sub-components
| Component | Element | Purpose |
|---|---|---|
RadioGroup | <div> | Root container with role="radiogroup" managing selection state |
RadioGroupItem | <button> | Individual radio option with role="radio" and circle indicator (or, in the card variant, icon, title, description and check mark) |
Accessibility
- Built on
@radix-ui/react-radio-groupwith full WAI-ARIA radio group semantics RadioGrouprenders withrole="radiogroup"; eachRadioGroupItemrenders withrole="radio"- Wrap the group in a
<Fieldset>with a<FieldsetLegend>to give the group an accessible name - Each item should have an associated
<Label>via matchinghtmlFor/id - Keyboard: Arrow keys move selection within the group and skip disabled items; Tab moves focus to/from the group
- Card variant: the title names the item (
aria-labelledby), the description and anyaria-describedbyid describe it, and the chosen card carries a check mark as well as a fill