Video examples
iOS Voiceover
Android Talkback
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" aria-describedby="hint-charlie" checked>
<label for="charlieRadio">Charlie</label>
<div class="hint" id="hint-charlie">This is the best letter</div>
</fieldset>
Fully disabled radio inputs
<fieldset>
<legend>
Choose your favorite coffee chain
</legend>
<input type="radio" name="coffee" id="starbucksRadio">
<label for="starbucksRadio">Starbucks</label>
<input type="radio" name="coffee" id="dutchRadio" checked>
<label for="dutchRadio">Dutch Brothers</label>
<input type="radio" name="coffee" id="dunkinRadio" disabled>
<label for="dunkinRadio">Dunkin'</label>
</fieldset>
Disabled and focusable radio inputs
It’s possible to use aria-disabled="true"
so screen reader users can focus the radio button with the arrow keys. Use preventDefault()
to prevent the checkbox from being checked.
When specific radio inputs are conditionally enabled/disabled by other controls in the page this method can make ensure all radio input options are discoverable.
<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>
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 thearia-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>
Radio button cards
<ul class="cards">
<li class="card interactive">
<input type="radio"
name="radioCards"
id="deltaRadioCard"
aria-describedby="hint-deltaRadioCard" >
<label for="deltaRadioCard">
Delta
</label>
<div class="extended-description"
id="hint-deltaRadioCard">
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="hint-echoRadioCard">
<label for="echoRadioCard">Echo</label>
<div class="extended-description"
id="hint-echoRadioCard">
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>
Specialty use cases
Radio mixed with interactive elements
Avoid placing interactive elements between radio buttons.
- 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.
Checkbox radio hack
- This hack must be used very carefully on a case by case basis.
- With great power comes great responsibility.
<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>
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 includefor="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
- Use
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
- Use
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