Video examples

iOS Voiceover Safari

Android Talkback Chrome

MacOS Voiceover Safari

Code examples

Use semantic HTML

  • This semantic HTML contains all accessibility features by default.
  • It uses CSS pseudo attributes to create the radio indicator, no Javascript.
<fieldset>
  <legend>
    Choose your favorite NATO letter
  </legend>

  <input type="radio" name="nato" id="alphaRadio">
  <label for="alphaRadio">Alpha</label>

  <input type="radio" name="nato" id="bravoRadio">
  <label for="bravoRadio">Bravo</label>

  <input type="radio" name="nato" id="charlieRadio" checked>
  <label for="charlieRadio">Charlie</label>
</fieldset>
Choose your favorite NATO letter

Disabled and focusable radio inputs (preferred)

  • An input using aria-disabled="true will be focusable with the tab key
  • Use JS to preventDefault()
<fieldset>
  <legend>
    Choose your favorite dance
  </legend>

  <input type="radio" name="dance" id="carltonRadio" aria-disabled="true">
  <label for="carltonRadio">Carlton</label>

  <input type="radio" name="dance" id="foxtrotRadio">
  <label for="foxtrotRadio">Foxtrot</label>

  <input type="radio" name="dance" id="tangoRadio" checked>
  <label for="tangoRadio">Tango</label>
</fieldset>
Choose your favorite dance

Required radio inputs

Ensuring all screenreaders indicate radio inputs as being required requires some aria and reinforcement.

  • Use aria-required="true" to indicate the group is required
  • Use aria-invalid="true/false" to indicate an error state
  • Add role="radiogroup" to the <fieldset> to make the aria-required attribute valid
  • Add “Required” as text to the <legend> to ensure compliance across all platforms
<fieldset aria-required="true" 
          aria-invalid="true" 
          role="radiogroup">
  <legend>
    Choose your second favorite NATO letter <span>Required</span>
  </legend>

  <input type="radio" name="natoReq" id="deltaRadioReq">
  <label for="deltaRadioReq">Delta</label>

  <input type="radio" name="natoReq" id="echoRadioReq">
  <label for="echoRadioReq">Echo</label>

  <input type="radio" name="natoReq" id="foxtrotRadioReq">
  <label for="foxtrotRadioReq">Foxtrot</label>
</fieldset>
Choose your second favorite NATO letter Required

Radio button cards

<ul class="cards">
  <li class="card interactive">
    <input type="radio"
           name="radioCards"
           id="deltaRadioCard"
           aria-describedby="descriptionDelta" >
    <label for="deltaRadioCard">
      Delta
    </label>
    <div class="extended-description"
         id="descriptionDelta">
      Delta (prounounced: <strong>dell</strong>-tah)
      is the fourth letter of the NATO alphabet.
    </div>
  </li>
  <li class="card interactive">
    <input type="radio"
           name="radioCards"
           id="echoRadioCard"
           aria-describedby="descriptionEcho">
    <label for="echoRadioCard">Echo</label>
    <div class="extended-description"
         id="descriptionEcho">
      Echo (prounounced: <strong>eck</strong>-oh)
      is the fifth letter of the NATO alphabet.
    </div>
  </li>
</ul>
  • Delta (prounounced: dell-tah) is the fourth letter of the NATO alphabet.
  • Echo (prounounced: eck-oh) is the fifth letter of the NATO alphabet.

When you can’t use semantic HTML

This custom button requires extra scripting work for roving tabindex and event listeners.

<custom-label id="labelId">
    Which is your favorite NATO letter:
</custom-label>
<div role="radiogroup" aria-labelledby="labelId">
  <custom-element role="radio" tabindex="-1">
    Alpha
  </custom-element>
  <custom-element role="radio" tabindex="-1">
    Bravo
  </custom-element>
  <custom-element role="radio" tabindex="-1">
    Charlie
  </custom-element>  
</div>

Speciality use cases

Radio with interactive elements

When a UI calls for interactive elements inbetween radio buttons, this can be very difficult.

  • Radio button focus order is not what you think it is.
  • When nothing is selected, tab order moves through as expected.
  • However, as soon as a radio button is selected, the selected radio input receives focus first from the group.

This hack must be used very carefully on a case by case basis.

<fieldset class="checkbox-radio-group">
  <legend>Choose your payment method:</legend>
  <input class="radio"
         type="checkbox"
         role="radio"
         name="checkboxRadioGroup"
         id="checkboxRadioAlpha"
         aria-describedby="editAlpha"
         checked>
  <label for="checkboxRadioAlpha">
    Alpha
  </label>
  <button type="button"
          class="tertiary"
          id="editAlpha">
    Edit
    <span class="hidden">
      payment method alpha
    </span>
  </button>

  <input  class="radio"
          type="checkbox"
          role="radio"
          name="checkboxRadioGroup"
          id="checkboxRadioBravo"
          aria-describedby="editBravo">
  <label for="checkboxRadioBravo">
    Bravo
  </label>
  <button type="button"
          class="tertiary"
          id="editBravo">
    Edit
    <span class="hidden">
      payment method Bravo
    </span>
  </button>

  <input class="radio"
         type="checkbox"
         role="radio"
         name="checkboxRadioGroup"
         id="checkboxRadioCharlie"
         aria-describedby="editCharlie">
  <label for="checkboxRadioCharlie">
    Charlie
  </label>
  <button type="button"
          class="tertiary"
          id="editCharlie">
    Edit
    <span class="hidden">
      payment method Charlie
    </span>
  </button>
</fieldset>
Choose your payment method:

Developer notes

Name

  • label text must describe the radio input.
  • Use aria-describedby="hint-id" for hints or additional descriptions
  • aria-label="Radio input purpose" can also be used (as a last resort)

Role

  • By default, semantic HTML radio inputs identify as radio button
  • Use role="radio" for custom elements

Group

  • Semantic HTML
    • <fieldset> must wrap the radio group
    • <legend> must describe the group’s purpose
    • Each <label> must include for="input-id" to be associated with its input
  • Custom elements
    • Use role="radiogroup" to take the palace of fieldset
    • Use aria-labelledby="label-id" to associate an element as a label
    • aria-label="Group purpose" can also be used if there’s no label with an ID

State

  • Semantic HTML
    • checked (will be read as “selected” by screen reader)
    • Use the disabled state for inactive buttons
  • Custom element
    • Use aria-checked="true/false" to express state
    • Use aria-disabled="true" to declare inactive elements

Focus

  • Focus must be visible
  • Custom elements will require keyboard event listeners and roving tabindex
  • DO NOT put interactive elements inbetween radio inputs.
    • Performs its purpose across platforms, devices and viewports

Thanks