Accessibility pattern · Form inputs

Radio group

Native radios with one name are a group already: one Tab stop, arrow keys that move and select, and "2 of 3" announced for free. The fieldset's legend asks the question, each option's description and price join it through aria-describedby, and the dot is drawn in CSS without losing any of that.

WCAG criteria
7
Keyboard rules
5
Checked with
axe, keyboard and the inspector

Live demo

Try it

Use it with a mouse, a keyboard or a screen reader. The inspector beside it shows what the browser tells assistive technology as you go: focus, state changes and announcements.

Checkout

To Kothrud, Pune · 2 items

Delivery speed

Days are working days. Prices include tax.

5 to 7 days

Free

2 to 3 days

₹99

Tomorrow, if you order by 2 pm

₹249

The demo works without JavaScript only as far as its HTML does; the inspector needs JavaScript.

Keyboard

Keys it answers to

Every action works without a pointer.

KeyWhat it does
TabMoves into the group: onto the chosen option, or the first one if none is chosen yet. The next Tab leaves the group.
Arrow Down or Arrow RightMoves to the next option and selects it, wrapping from the last to the first.
Arrow Up or Arrow LeftMoves to the previous option and selects it.
SpaceSelects the focused option, when none is selected yet.
EnterSends the choice, from an option or from Continue to payment. With nothing chosen, focus moves to the first option and the error is read with it.

Screen readers

What it announces

Written from the roles, names and states in the markup.

WhenExpected announcement
Tab enters the group with nothing chosenDelivery speed, group. Days are working days… Standard, radio button, not checked, required, 1 of 3. 5 to 7 days Free
Arrow DownExpress, radio button, checked, 2 of 3. 2 to 3 days ₹99
Continue is pressed with nothing chosenDelivery speed, group. Standard, radio button, not checked, invalid entry, required, 1 of 3. 5 to 7 days Free Choose a delivery speed
An option is then chosenChecked (the error clears)
The choice is sentDelivery speed saved

These are expected announcements, not recordings. Wording and order differ between screen readers and browsers.

Code

Copy the code

The exact files this demo runs on. The styles are served with a prefix that keeps this site's own styles out of the demo; what you copy is the original.

The markup

<form class="ap-radio-group" data-ap-radio-group novalidate aria-labelledby="rg-form-name">
  <div class="ap-radio-group__top">
    <h3 class="ap-radio-group__name" id="rg-form-name">Checkout</h3>
    <p class="ap-radio-group__sub">To Kothrud, Pune · 2 items</p>
  </div>

  <fieldset class="ap-radio-group__set" aria-describedby="rg-hint">
    <legend class="ap-radio-group__legend">Delivery speed <span class="ap-radio-group__req" aria-hidden="true">Required</span></legend>
    <p class="ap-radio-group__hint" id="rg-hint">Days are working days. Prices include tax.</p>
    <p class="ap-error ap-radio-group__error" id="rg-error" hidden><svg class="ap-radio-group__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="9"/><path d="M12 7.5v5"/><path d="M12 16.5h.01"/></svg><span>Choose a delivery speed</span></p>
    <div class="ap-radio-group__options">
      <div class="ap-radio-group__option">
        <input class="ap-radio-group__radio" type="radio" id="rg-standard" name="speed" value="standard" required aria-describedby="rg-standard-desc rg-standard-price" />
        <div class="ap-radio-group__text">
          <label class="ap-radio-group__label" for="rg-standard">Standard</label>
          <p class="ap-radio-group__desc" id="rg-standard-desc">5 to 7 days</p>
        </div>
        <p class="ap-radio-group__price" id="rg-standard-price">Free</p>
      </div>
      <div class="ap-radio-group__option">
        <input class="ap-radio-group__radio" type="radio" id="rg-express" name="speed" value="express" required aria-describedby="rg-express-desc rg-express-price" />
        <div class="ap-radio-group__text">
          <label class="ap-radio-group__label" for="rg-express">Express</label>
          <p class="ap-radio-group__desc" id="rg-express-desc">2 to 3 days</p>
        </div>
        <p class="ap-radio-group__price" id="rg-express-price">₹99</p>
      </div>
      <div class="ap-radio-group__option">
        <input class="ap-radio-group__radio" type="radio" id="rg-next-day" name="speed" value="next-day" required aria-describedby="rg-next-day-desc rg-next-day-price" />
        <div class="ap-radio-group__text">
          <label class="ap-radio-group__label" for="rg-next-day">Next day</label>
          <p class="ap-radio-group__desc" id="rg-next-day-desc">Tomorrow, if you order by 2 pm</p>
        </div>
        <p class="ap-radio-group__price" id="rg-next-day-price">₹249</p>
      </div>
    </div>
  </fieldset>

  <div class="ap-radio-group__foot">
    <p class="ap-radio-group__status" role="status"></p>
    <button type="submit" class="ap-btn ap-btn--primary">Continue to payment</button>
  </div>
</form>

WCAG 2.2

What it meets

The success criteria this pattern takes care of, and how.

  • 1.3.1 Info and Relationships Level A

    The fieldset and legend name the group, and each option's description and price are joined to it with aria-describedby.

  • 1.4.1 Use of Color Level A

    The chosen option has a filled dot and a thicker accent edge, not just a tint, and the error has an icon and words.

  • 1.4.11 Non-text Contrast Level AA

    The ring, the dot and the chosen edge all clear 3:1 against the card in light and dark.

  • 2.1.1 Keyboard Level A

    The browser's own arrow keys, Space and Tab work, because the inputs are real radios sharing one name.

  • 2.5.8 Target Size (Minimum) Level AA

    Each whole option card is the label, so every target is far larger than 24 by 24 pixels.

  • 3.3.1 Error Identification Level A

    Continuing without a choice shows "Choose a delivery speed" under the legend and marks each radio aria-invalid.

  • 4.1.2 Name, Role, Value Level A

    Each radio reports its name, checked state, position in the set and required state natively.

Usage

When to use it

Use it

  • Choosing exactly one of a few options that people should compare side by side, like delivery speeds or plans.
  • Up to about seven options; beyond that, a select or a listbox is easier to scan.

Use something else

  • Options that can be combined: use checkboxes.
  • A single on-or-off setting: use a checkbox or a switch.
  • Switching between views of content: use tabs or a segmented control.

Common failures

How it usually goes wrong

  • Radios without a fieldset

    "Express, radio button" means little on its own. The legend says what the choice is about, and screen readers read it as focus enters the group.

  • A custom radio built from divs

    Rebuilding arrow keys, roving focus, the checked state and the "2 of 3" count is a lot of code to get wrong. A native input with appearance: none keeps all of it.

  • Every radio in the Tab order

    Adding tabindex to each option breaks the group's one-stop model. Native radios with one name already behave correctly.

  • Price and details outside the option

    If the price is a separate line nobody connects, people choose without hearing the cost. It is part of each option's description here.

  • Preselecting a choice that costs money

    A default that charges more is a dark pattern; no default plus a clear error when nothing is chosen respects the decision.

Notes

Building it

  • Put required on the radios themselves; one required radio makes the whole group required, and each announces it.
  • The error sits between the legend and the options, so it is seen before the choices, and it joins every radio's description while it shows.
  • The compact option lays the cards out in a row on wide screens; below about 460 pixels they stack again, so nothing scrolls sideways.
  • aria-invalid is set on the radios, not the fieldset: a group role does not take it, and the radios are what gets focus.
  • Enter on a radio sends the form. If focus is already on the first option, the script moves it off and back, so the option and its new error are read again.

Sources: WAI-ARIA Authoring Practices: Radio Group · WAI Tutorials: Grouping controls

Checked with axe in light and dark themes, at desktop and phone widths, and by keyboard. Not yet tested with every screen reader and browser pair. Report a correction