Radio
Mutually exclusive selection within a group. Use RadioGroup as the container and Radio for each option — they share state via React context.
Preview
Orientation
Horizontal
Sizes
| Size | Outer circle | Inner dot |
|---|---|---|
| sm | 14 × 14px | 6 × 6px |
| md | 16 × 16px | 8 × 8px |
| lg | 20 × 20px | 10 × 10px |
Implementation
Radio.tsx
tsx
import { RadioGroup, Radio } from "@/components/ui/Radio";
import { useState } from "react";
// Vertical group (default)
const [plan, setPlan] = useState("monthly");
<RadioGroup
label="Billing plan"
value={plan}
onChange={setPlan}
name="billing"
>
<Radio value="monthly" label="Monthly" helperText="$12/month" />
<Radio value="annual" label="Annual" helperText="$8/month, billed annually" />
<Radio value="team" label="Team" helperText="$25/month per user" />
</RadioGroup>
// Horizontal layout
<RadioGroup
label="Size"
orientation="horizontal"
value={size}
onChange={setSize}
name="size"
>
<Radio value="sm" label="Small" />
<Radio value="md" label="Medium" />
<Radio value="lg" label="Large" />
</RadioGroup>
// With one option disabled
<RadioGroup label="Region" value={region} onChange={setRegion} name="region">
<Radio value="us" label="United States" />
<Radio value="eu" label="Europe" />
<Radio value="asia" label="Asia Pacific" disabled helperText="Coming soon" />
</RadioGroup>Props — RadioGroup
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | Accessible group label, rendered as a <legend> inside a <fieldset>. |
helperText | string | — | Supplemental hint rendered below the group. |
orientation | "vertical" | "horizontal" | "vertical" | Stacks options vertically or lays them out in a row. |
size | "sm" | "md" | "lg" | "md" | Applies to all Radio children unless overridden individually. |
value | string | — | Controlled selected value. |
onChange | (value: string) => void | — | Called when a Radio is selected. |
name | string | — | Name attribute shared across all Radio inputs in the group. |
disabled | boolean | false | Disables all Radio children. |
Props — Radio
All native <input type="radio"> attributes are forwarded. Size and name are inherited from RadioGroup context.
| Prop | Type | Default | Description |
|---|---|---|---|
value* | string | — | The value this radio represents. Matched against RadioGroup's value prop. |
label | string | — | Visible label for this option. |
helperText | string | — | Supplemental hint rendered below the label. |
disabled | boolean | false | Disables this individual radio option. |
ARIA roles
| Element | Role | Key attributes |
|---|---|---|
| Group | radiogroup | aria-labelledby, aria-describedby |
| Option | radio (implicit) | aria-checked (true/false) |
Keyboard
| Key | Action |
|---|---|
| Arrow Down / Right | Move focus to and select the next option in the group |
| Arrow Up / Left | Move focus to and select the previous option in the group |
| Tab | Move focus into / out of the group (single tab stop) |
| Shift+Tab | Move focus backward past the group |
Accessibility
- →RadioGroup renders a <fieldset> with a <legend>, giving the group an accessible name read by screen readers.
- →Each Radio is a native <input type="radio"> — arrow keys navigate within the group natively.
- →The visual indicator is CSS-only; the real input is sr-only and receives focus.
- →Disabled radio options set aria-disabled and visually dim both the control and label.
- →helperText on individual options should be linked via aria-describedby for screen reader context.