Radio group

A radio group is a set of mutually exclusive options where exactly one can be selected at a time. The whole group has a single accessible name, the user moves between options with the arrow keys, and choosing one option automatically clears the rest. Because selection is single-valued, the group as a whole acts like one form control — Tab lands on it once, not on every option in turn.

Reach for native HTML first: a set of <input type="radio"> controls that share a name, wrapped in a <fieldset> with a <legend>, gives you the full keyboard model, grouping, and screen-reader semantics for free. Build the custom role="radiogroup" widget below only when you genuinely cannot use the native control — and budget for implementing the entire roving-tabindex keyboard model yourself.

Live demo

Tab into the group: focus lands on the selected option (or the first, if none is selected yet). Press the arrow keys to move between options — each move also selects the option it lands on — and press Space to (re)select the focused option. Only one option is in the tab order at a time, so a second Tab leaves the group entirely.

Contrast level to target
AA (minimum) AA (recommended) AAA (enhanced)

When to use

Prefer the native control first

  • Use a radio group when the user must pick exactly one option from a small, fixed set of mutually exclusive choices that are all visible at once.
  • If more than a handful of options compete for space, or the list is long, a <select> may serve better.
  • When several options can be chosen independently, use checkboxes, not radios.
  • Whenever you can, build the group from native <input type="radio"> controls inside a <fieldset> with a <legend>; only fall back to the ARIA pattern below when custom rendering forces it.

Markup

Author the group and its options statically with the ARIA wiring in place: the group names itself with aria-labelledby, every option carries aria-checked, and exactly one option is tabbable (tabindex="0") while the rest are -1. Then radio-group.js manages the roving tabindex and selection.

radio.html
<div class="ewa-radiogroup" role="radiogroup"
     aria-labelledby="rg-l" data-radiogroup>
  <span class="ewa-radiogroup__label" id="rg-l">Contrast level to target</span>
  <div class="ewa-radiogroup__options">
    <span class="ewa-radio" role="radio" aria-checked="false" tabindex="-1">AA (minimum)</span>
    <span class="ewa-radio" role="radio" aria-checked="true"  tabindex="0">AA (recommended)</span>
    <span class="ewa-radio" role="radio" aria-checked="false" tabindex="-1">AAA (enhanced)</span>
  </div>
</div>

<!-- Preferred whenever possible: the native control -->
<fieldset>
  <legend>Contrast level to target</legend>
  <label><input type="radio" name="contrast" value="aa-min"> AA (minimum)</label>
  <label><input type="radio" name="contrast" value="aa" checked> AA (recommended)</label>
  <label><input type="radio" name="contrast" value="aaa"> AAA (enhanced)</label>
</fieldset>

Keyboard interactions

The group is a single tab stop. Once focus is inside, the arrow keys move and select in one motion, and Space selects the option that already has focus.

Keyboard interactions (the group is one tab stop)
Key Action
Tab Moves focus into the group, landing on the checked option (or the first option if none is checked). A second Tab moves focus out of the group.
Down Arrow / Right Arrow Moves focus to the next option and checks it; wraps from the last option to the first.
Up Arrow / Left Arrow Moves focus to the previous option and checks it; wraps from the first option to the last.
Space Checks the focused option (and unchecks the previously checked one).
Home / End Optional: moves focus to and checks the first / last option.

ARIA roles, states, and properties

The container owns the group role and its accessible name; each option owns the radio role and its checked state. A roving tabindex keeps the group a single tab stop.

ARIA attributes and where they live
Attribute On Purpose
role="radiogroup" Container Marks the wrapper as a single-select group of radios.
aria-labelledby Container Gives the group its accessible name from the visible group label (or use aria-label if there is no visible text).
role="radio" Each option Marks each item as one selectable radio within the group.
aria-checked Each option true on the one selected option, false on every other.
tabindex="0" One option Exactly one option is tabbable — the checked one, or the first when none is checked — so the group is a single tab stop.
tabindex="-1" Other options Keeps the remaining options out of the tab order; the arrow keys move focus between them programmatically (roving tabindex).

Common mistakes

Pitfalls to avoid

  • Reinventing the native control. Prefer <input type="radio"> inside a <fieldset><legend>. Build a custom role="radiogroup" only when you truly cannot use it — and then implement the full arrow-key roving-tabindex model, not just clicks.
  • Leaving every option in the tab order. A radio group is one tab stop. Exactly one option carries tabindex="0"; the rest are -1 and are reached with the arrow keys.
  • Forgetting that arrows also select. In a radio group, moving with the arrow keys checks the option focus lands on — it does not just move a highlight.
  • Skipping the group name. The role="radiogroup" needs an accessible name via aria-labelledby or aria-label, or the options are announced without context.
  • Showing selection by colour alone. Pair the checked state with a non-colour cue (a filled control dot) and remap it to system colours under forced colours.