Skip to content

Find what you need, instantly

Search components, ARIA roles & attributes, and WCAG criteria.

Forms & Controls

Radio Group

A set of mutually exclusive selectable options.

Last verified against WCAG 2.2 and WAI-ARIA APG 1.2 on 2026-07-02.

Implementation

radio-group-pattern.tsx
function RadioGroupPattern() {
  const [selected, setSelected] = useState("standard");
  const optionRefs = useRef({});

  function onKeyDown(e, index) {
    let nextIndex = null;
    if (e.key === "ArrowDown" || e.key === "ArrowRight") nextIndex = (index + 1) % OPTIONS.length;
    if (e.key === "ArrowUp" || e.key === "ArrowLeft") nextIndex = (index - 1 + OPTIONS.length) % OPTIONS.length;
    if (nextIndex !== null) {
      e.preventDefault();
      const next = OPTIONS[nextIndex];
      setSelected(next.id); // arrowing selects immediately — no separate activation step
      optionRefs.current[next.id]?.focus();
    }
  }

  return (
    <div role="radiogroup" aria-label="Shipping speed">
      {OPTIONS.map((option, i) => {
        const checked = selected === option.id;
        return (
          <button
            key={option.id}
            ref={el => (optionRefs.current[option.id] = el)}
            role="radio"
            aria-checked={checked}
            tabIndex={checked ? 0 : -1} // roving tabindex: only the checked option is a Tab stop
            onClick={() => setSelected(option.id)}
            onKeyDown={e => onKeyDown(e, i)}
          >
            {option.label}
          </button>
        );
      })}
    </div>
  );
}

Live demo

Required roles, states & properties

Required roles, states, and properties
ElementAttributeWhy
Group wrapperrole="radiogroup"Identifies the container as a set of mutually exclusive options, distinct from an unrelated list of buttons.
Group wrapperaria-label (or aria-labelledby)Gives the group itself an accessible name (e.g. "Shipping speed") announced when a screen reader user enters it.
Each option <button>role="radio"Overrides default button semantics so AT announces "radio button" and exposes the checked state below.
Each option <button>aria-checkedCommunicates which single option is currently selected; exactly one radio in the group should be true at a time.
Each option <button>tabIndex (roving 0 / -1)Only the selected option (or the first, before any selection) is in the Tab order — matching native <input type="radio"> grouping behavior, where arrow keys move between the rest.

Keyboard interaction model

Keyboard interaction model
KeyBehavior
Tab / Shift+TabMoves focus into or out of the whole group, landing only on the currently selected option (roving tabindex).
Arrow Down / RightMoves focus to the next option AND selects it immediately — radio groups select-on-arrow, unlike tabs where activation mode is configurable.
Arrow Up / LeftMoves focus to the previous option AND selects it immediately.
SpaceSelects the focused option (redundant with arrow-key selection, but expected for consistency with checkboxes).

Focus management rules

  • Only one option is ever in the Tab order at a time: the selected option, or the first option before any selection is made.
  • Arrow keys move focus between options AND change the selection in the same action — there is no separate "confirm" step.
  • Tabbing out of and back into the group always returns focus to the currently selected option, not the first in list order.

WCAG 2.2 success criteria mapping

WCAG 2.2 success criteria that apply to this component
SCNameLevelWhy it applies
1.3.1Info and RelationshipsAStructure and relationships conveyed visually are also programmatically determinable.
2.1.1KeyboardAAll functionality is operable through a keyboard interface with no specific timing.
4.1.2Name, Role, ValueAFor all UI components, name, role, and value are programmatically determinable; states and changes are announced.

References