Native groups with <fieldset> and <legend>, exclusive radio groups, custom-styled inputs that stay accessible, and the indeterminate “select all” tri-state pattern — Vanilla & React.
WCAG: 1.3.1 · 1.4.11 · 2.1.1 · 4.1.2
Level: Beginner → Intermediate
Icons: lucide-react (Check)
1
What we are building
Native checkbox and radio controls, grouped so SR users know what the choice is about.
What we are building
Native checkbox and radio inputs already give you keyboard, SR, focus, state, and form submission for free. The only things you must do: group related controls in a fieldset with a legend, share a name across radios in the same group, and if you want custom visuals, keep the native input in the DOM (visually hidden, not removed).
2
The 4 real-world variants
Switch between the inaccessible version and the accessible fix.
The 4 real-world variants
Variant A
Checkbox group
Preferred
Live example
SR announces: “Email notifications, group, Product updates, checked, checkbox, 1 of 3”.
HTML · Accessible
<!-- fieldset + legend groups them; SR announces group on EVERY option -->
<fieldset>
<legend>Email notifications</legend>
<label><input type="checkbox" name="prefs" value="product"> Product updates</label>
<label><input type="checkbox" name="prefs" value="security"> Security alerts</label>
<label><input type="checkbox" name="prefs" value="marketing"> Marketing & promotions</label>
</fieldset>
fieldset + legend groups them. SR announces the legend before each option so users know what the choice is for.
Variant B
Radio group
Preferred
Live example
Tab enters the group. ↑↓ moves AND selects. All radios share the same name.
HTML · Accessible
<!-- All radios share the same name — browser handles exclusivity -->
<fieldset>
<legend>Shipping speed</legend>
<label><input type="radio" name="speed" value="std"> Standard (5-7 days)</label>
<label><input type="radio" name="speed" value="exp" checked> Express (2 days)</label>
<label><input type="radio" name="speed" value="ovn"> Overnight</label>
</fieldset>
All radios in the group share the same `name`. Browser handles exclusivity; arrow keys move AND select between options.
Variant C
Custom-styled
When needed
Live example
The native input is still there (focusable, keyboard, SR). Only the visual box is styled.