Standards · ARIA

Role Widget

radio

Marks an element as a single option in a mutually exclusive group. A radio must live inside a radiogroup (or a native fieldset). Use <input type="radio"> first; reach for role="radio" only when the native input is impossible.

When to use

Use <input type="radio"> grouped by a shared name attribute, wrapped in <fieldset> with a <legend>. Native handles arrow-key navigation, the roving tabindex, exclusive selection, and form submission for free.

role="radio" is for custom components — segmented controls, rating widgets, themed pickers — where you cannot use the native input. A role="radio" element MUST have a parent (or aria-owns target) with role="radiogroup".

Keyboard + focus contract

Per the APG radio group pattern:

  • Tab moves focus into the group, onto the checked radio (or the first radio if none is checked).
  • Tab moves focus OUT of the group. Tab does NOT cycle between radios.
  • Arrow keys (Up/Down or Left/Right) move focus to the next/previous radio AND check it. This is unusual — most widgets separate focus from selection, but radios couple them.
  • Space checks the focused radio if it isn’t already checked.

Use a roving tabindex: only the currently selected radio has tabindex="0", the rest have tabindex="-1".

Common failures

  • Custom radios where Tab cycles between every option (instead of arrow keys). Breaks the standard radio interaction model.
  • role="radio" without a wrapping role="radiogroup". Screen readers cannot announce the group label or the position-in-set.
  • All radios with tabindex="0" — every radio enters the tab order, multiplying keystrokes.
  • Missing aria-checked on initial render.
  • Custom radios that toggle off when re-pressed. A radio is exclusive: once checked, the only way to uncheck it is to check another in the group.

Example

<!-- Preferred -->
<fieldset>
  <legend>Notification frequency</legend>
  <label><input type="radio" name="freq" value="daily" checked> Daily</label>
  <label><input type="radio" name="freq" value="weekly"> Weekly</label>
  <label><input type="radio" name="freq" value="never"> Never</label>
</fieldset>

<!-- Custom radio group -->
<div role="radiogroup" aria-labelledby="freqLabel">
  <span id="freqLabel">Notification frequency</span>
  <div role="radio" aria-checked="true"  tabindex="0">Daily</div>
  <div role="radio" aria-checked="false" tabindex="-1">Weekly</div>
  <div role="radio" aria-checked="false" tabindex="-1">Never</div>
</div>