Radio

Mutually exclusive selection within a group. Use RadioGroup as the container and Radio for each option — they share state via React context.

Preview

Billing plan

$12/month

$8/month, billed annually

$25/month per user

Orientation

Horizontal
Notification frequency

Sizes

Size: sm
Size: md
Size: lg
SizeOuter circleInner dot
sm14 × 14px6 × 6px
md16 × 16px8 × 8px
lg20 × 20px10 × 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

PropTypeDefaultDescription
labelstringAccessible group label, rendered as a <legend> inside a <fieldset>.
helperTextstringSupplemental 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.
valuestringControlled selected value.
onChange(value: string) => voidCalled when a Radio is selected.
namestringName attribute shared across all Radio inputs in the group.
disabledbooleanfalseDisables all Radio children.

Props — Radio

All native <input type="radio"> attributes are forwarded. Size and name are inherited from RadioGroup context.

PropTypeDefaultDescription
value*stringThe value this radio represents. Matched against RadioGroup's value prop.
labelstringVisible label for this option.
helperTextstringSupplemental hint rendered below the label.
disabledbooleanfalseDisables this individual radio option.

ARIA roles

ElementRoleKey attributes
Groupradiogrouparia-labelledby, aria-describedby
Optionradio (implicit)aria-checked (true/false)

Keyboard

KeyAction
Arrow Down / RightMove focus to and select the next option in the group
Arrow Up / LeftMove focus to and select the previous option in the group
TabMove focus into / out of the group (single tab stop)
Shift+TabMove 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.