Accessibility pattern · Buttons and actions

Segmented control

A segmented control is a radio group in different clothes: radio inputs inside a fieldset bring the arrow keys, the single tab stop and the checked state with them. The thumb that slides behind the chosen segment is decoration; the checked radio is what assistive technology reports.

WCAG criteria
7
Keyboard rules
4
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.

Sprint 14

5 tasks · ends Friday

Layout

To do 2

  • Write the launch brief Asha
  • Book the venue in Pune Ravi

In progress 2

  • Design the invite Meera
  • Update the price list Kabir

Done 1

  • Pick the launch date Asha

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 to the selected segment; the next Tab leaves the control.
Arrow Right or Arrow DownSelects the next layout, wrapping from the last to the first, and the preview changes at once.
Arrow Left or Arrow UpSelects the previous layout, wrapping from the first to the last.
SpaceSelects the focused segment, if none is selected yet.

Screen readers

What it announces

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

WhenExpected announcement
Tab reaches the controlLayout, group. List, radio button, checked, 1 of 3
Arrow Right selects the next segmentGrid, radio button, checked, 2 of 3
Icons only is on and Arrow Right reaches the last segmentBoard, radio button, checked, 3 of 3

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

<div class="ap-segmented-control" data-ap-segmented-control>
  <div class="ap-segmented-control__top">
    <div class="ap-segmented-control__id">
      <h3 class="ap-segmented-control__label">Sprint 14</h3>
      <p class="ap-segmented-control__sub">5 tasks · ends Friday</p>
    </div>
    <fieldset class="ap-segmented-control__set">
      <legend class="ap-segmented-control__legend">Layout</legend>
      <div class="ap-segmented-control__track">
        <label class="ap-segmented-control__seg">
          <input class="ap-segmented-control__radio" type="radio" name="segmented-control-layout" value="list" checked />
          <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M8 6h12M8 12h12M8 18h12M4 6h.01M4 12h.01M4 18h.01"/></svg>
          <span class="ap-segmented-control__text">List</span>
        </label>
        <label class="ap-segmented-control__seg">
          <input class="ap-segmented-control__radio" type="radio" name="segmented-control-layout" value="grid" />
          <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><rect x="4" y="4" width="7" height="7" rx="1.5"/><rect x="13" y="4" width="7" height="7" rx="1.5"/><rect x="4" y="13" width="7" height="7" rx="1.5"/><rect x="13" y="13" width="7" height="7" rx="1.5"/></svg>
          <span class="ap-segmented-control__text">Grid</span>
        </label>
        <label class="ap-segmented-control__seg">
          <input class="ap-segmented-control__radio" type="radio" name="segmented-control-layout" value="board" />
          <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><rect x="3.5" y="4" width="5" height="16" rx="1.5"/><rect x="9.5" y="4" width="5" height="11" rx="1.5"/><rect x="15.5" y="4" width="5" height="7" rx="1.5"/></svg>
          <span class="ap-segmented-control__text">Board</span>
        </label>
        <span class="ap-segmented-control__thumb" aria-hidden="true"></span>
      </div>
    </fieldset>
  </div>

  <div class="ap-segmented-control__preview" data-ap-preview data-view="list">
    <div class="ap-segmented-control__group">
      <h4 class="ap-segmented-control__group-name"><span>To do</span> <span class="ap-segmented-control__n">2</span></h4>
      <ul class="ap-segmented-control__cards">
          <li class="ap-segmented-control__card"><span class="ap-segmented-control__task">Write the launch brief</span> <span class="ap-segmented-control__who">Asha</span></li>
          <li class="ap-segmented-control__card"><span class="ap-segmented-control__task">Book the venue in Pune</span> <span class="ap-segmented-control__who">Ravi</span></li>
      </ul>
    </div>
    <div class="ap-segmented-control__group">
      <h4 class="ap-segmented-control__group-name"><span>In progress</span> <span class="ap-segmented-control__n">2</span></h4>
      <ul class="ap-segmented-control__cards">
          <li class="ap-segmented-control__card"><span class="ap-segmented-control__task">Design the invite</span> <span class="ap-segmented-control__who">Meera</span></li>
          <li class="ap-segmented-control__card"><span class="ap-segmented-control__task">Update the price list</span> <span class="ap-segmented-control__who">Kabir</span></li>
      </ul>
    </div>
    <div class="ap-segmented-control__group">
      <h4 class="ap-segmented-control__group-name"><span>Done</span> <span class="ap-segmented-control__n">1</span></h4>
      <ul class="ap-segmented-control__cards">
          <li class="ap-segmented-control__card"><span class="ap-segmented-control__task">Pick the launch date</span> <span class="ap-segmented-control__who">Asha</span></li>
      </ul>
    </div>
  </div>
</div>

WCAG 2.2

What it meets

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

  • 1.3.1 Info and Relationships Level A

    A fieldset and legend group the three choices and name them Layout, so the group is announced before the options.

  • 1.4.1 Use of Color Level A

    The chosen segment sits on a raised thumb with heavier text, so the choice never depends on color.

  • 1.4.11 Non-text Contrast Level AA

    The thumb's edge clears 3:1 against the track, so the chosen segment is visible to people who cannot see subtle fills.

  • 2.1.1 Keyboard Level A

    Native radio buttons answer to Tab and the arrow keys, so the control needs no key handling of its own.

  • 2.4.7 Focus Visible Level AA

    The focused segment shows a ring around the whole segment, drawn when its hidden radio matches :focus-visible.

  • 2.5.8 Target Size (Minimum) Level AA

    Each segment is at least 44 pixels tall and as wide as its label, well past the 24 by 24 minimum.

  • 4.1.2 Name, Role, Value Level A

    Each segment is a labelled radio input, so its name, role, position in the set and checked state come from the browser.

Usage

When to use it

Use it

  • Switching between two to five views or modes of the same content, like list and grid.
  • A setting with a few short options that are worth seeing side by side.

Use something else

  • Moving between sections of different content: use tabs, which tie each tab to its panel.
  • Long labels or more than five options: use a radio group or a select.
  • Turning one thing on or off: use a switch or a toggle button.

Common failures

How it usually goes wrong

  • Toggle buttons for a single choice

    A row of aria-pressed buttons lets several look pressed at once and makes every segment its own tab stop. Radios allow one choice and one stop.

  • Radios hidden with display: none

    display: none takes the input out of the keyboard order and the accessibility tree. Here the radio is transparent and covers its segment instead.

  • No name for the group

    Three radios called List, Grid and Board do not say what they change. The legend names the group Layout, visually hidden here but read first.

  • A focus ring on nothing

    When the real input is invisible, so is its focus ring. The ring is drawn on the whole segment with :has(:focus-visible).

  • Icons with no names

    Icon-only segments still need text for screen readers and voice control. With Icons only on, the label is visually hidden but still names the radio.

Notes

Building it

  • Radio buttons select as the arrow keys move, which suits a view that changes instantly. If a choice starts something slow, use buttons and confirm it instead.
  • The script sets one custom property, --seg-i, from the checked radio's position; equal-width segments turn that into a single translateX for the thumb.
  • Without JavaScript the checked segment gets its own fill instead of the sliding thumb, so the control still works and still shows its state.
  • In forced colors mode backgrounds disappear but borders remain, so the thumb's border keeps the chosen segment visible.

Sources: WAI-ARIA Authoring Practices: Radio Group · HTML: the radio button state

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