Accessibility pattern · Form inputs

Listbox

The list is one stop in the Tab order. Focus stays on the list while aria-activedescendant points at the option in focus, so screen readers follow the arrow keys. With Select several on, the list says it is multi-selectable, Space picks without moving, and a polite count says how many are selected.

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

Assign to

Type a name to jump to it.

1 selected

  • Arjun Mehta Engineering
  • Asha Rao Design
  • Divya Menon Finance
  • Farhan Ali Sales
  • Kabir Singh Marketing
  • Meera Iyer Product
  • Neha Joshi Legal
  • Priya Nair Support
  • Ravi Kumar Engineering
  • Vikram Das Research

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 list, onto the selected person; the next Tab leaves the list.
Arrow Down or Arrow UpMoves to the next or previous person. Choosing one person, the selection moves with it.
Home or EndMoves to the first or last person.
A–ZTyping the first letters of a name jumps to it; the same letter again moves on to the next name that starts with it.
SpaceWith Select several on, adds or removes the person in focus.
ShiftArrow Down or ShiftArrow UpWith Select several on, moves and selects every person from the last one picked to the one in focus.
CtrlAWith Select several on, selects everyone, or clears the selection when everyone is already selected.

Screen readers

What it announces

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

WhenExpected announcement
Tab reaches the listAssign to, list box. Asha Rao Design, selected, 2 of 10. Type a name to jump to it.
Arrow Down moves onDivya Menon Finance, selected, 3 of 10
Typing RRavi Kumar Engineering, selected, 9 of 10
With Select several on, Space picks Divya MenonSelected. 2 selected
Ctrl+A selects everyone10 selected

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-listbox" data-ap-listbox>
  <div class="ap-listbox__top">
    <div class="ap-listbox__intro">
      <p class="ap-listbox__label" id="listbox-label">Assign to</p>
      <p class="ap-listbox__hint" id="listbox-hint-one">Type a name to jump to it.</p>
      <p class="ap-listbox__hint" id="listbox-hint-many" hidden>Space picks a person. Shift with an arrow key picks the people next to them.</p>
    </div>
    <p class="ap-listbox__count" role="status"><span data-ap-n translate="no">1</span> <span>selected</span></p>
  </div>
  <ul class="ap-listbox__list" id="listbox-list" role="listbox" tabindex="0" aria-labelledby="listbox-label" aria-describedby="listbox-hint-one" aria-activedescendant="listbox-o-asha">
    <li class="ap-listbox__opt" role="option" id="listbox-o-arjun" aria-selected="false">
      <span class="ap-listbox__box" aria-hidden="true"><svg viewBox="0 0 24 24" focusable="false"><path d="m6 12.5 4 4 8-9"/></svg></span>
      <span class="ap-listbox__avatar" aria-hidden="true">AM</span>
      <span class="ap-listbox__who"><span class="ap-listbox__person">Arjun Mehta</span> <span class="ap-listbox__team">Engineering</span></span>
      <svg class="ap-listbox__tick" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg>
    </li>
    <li class="ap-listbox__opt" role="option" id="listbox-o-asha" aria-selected="true">
      <span class="ap-listbox__box" aria-hidden="true"><svg viewBox="0 0 24 24" focusable="false"><path d="m6 12.5 4 4 8-9"/></svg></span>
      <span class="ap-listbox__avatar" aria-hidden="true">AR</span>
      <span class="ap-listbox__who"><span class="ap-listbox__person">Asha Rao</span> <span class="ap-listbox__team">Design</span></span>
      <svg class="ap-listbox__tick" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg>
    </li>
    <li class="ap-listbox__opt" role="option" id="listbox-o-divya" aria-selected="false">
      <span class="ap-listbox__box" aria-hidden="true"><svg viewBox="0 0 24 24" focusable="false"><path d="m6 12.5 4 4 8-9"/></svg></span>
      <span class="ap-listbox__avatar" aria-hidden="true">DM</span>
      <span class="ap-listbox__who"><span class="ap-listbox__person">Divya Menon</span> <span class="ap-listbox__team">Finance</span></span>
      <svg class="ap-listbox__tick" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg>
    </li>
    <li class="ap-listbox__opt" role="option" id="listbox-o-farhan" aria-selected="false">
      <span class="ap-listbox__box" aria-hidden="true"><svg viewBox="0 0 24 24" focusable="false"><path d="m6 12.5 4 4 8-9"/></svg></span>
      <span class="ap-listbox__avatar" aria-hidden="true">FA</span>
      <span class="ap-listbox__who"><span class="ap-listbox__person">Farhan Ali</span> <span class="ap-listbox__team">Sales</span></span>
      <svg class="ap-listbox__tick" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg>
    </li>
    <li class="ap-listbox__opt" role="option" id="listbox-o-kabir" aria-selected="false">
      <span class="ap-listbox__box" aria-hidden="true"><svg viewBox="0 0 24 24" focusable="false"><path d="m6 12.5 4 4 8-9"/></svg></span>
      <span class="ap-listbox__avatar" aria-hidden="true">KS</span>
      <span class="ap-listbox__who"><span class="ap-listbox__person">Kabir Singh</span> <span class="ap-listbox__team">Marketing</span></span>
      <svg class="ap-listbox__tick" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg>
    </li>
    <li class="ap-listbox__opt" role="option" id="listbox-o-meera" aria-selected="false">
      <span class="ap-listbox__box" aria-hidden="true"><svg viewBox="0 0 24 24" focusable="false"><path d="m6 12.5 4 4 8-9"/></svg></span>
      <span class="ap-listbox__avatar" aria-hidden="true">MI</span>
      <span class="ap-listbox__who"><span class="ap-listbox__person">Meera Iyer</span> <span class="ap-listbox__team">Product</span></span>
      <svg class="ap-listbox__tick" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg>
    </li>
    <li class="ap-listbox__opt" role="option" id="listbox-o-neha" aria-selected="false">
      <span class="ap-listbox__box" aria-hidden="true"><svg viewBox="0 0 24 24" focusable="false"><path d="m6 12.5 4 4 8-9"/></svg></span>
      <span class="ap-listbox__avatar" aria-hidden="true">NJ</span>
      <span class="ap-listbox__who"><span class="ap-listbox__person">Neha Joshi</span> <span class="ap-listbox__team">Legal</span></span>
      <svg class="ap-listbox__tick" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg>
    </li>
    <li class="ap-listbox__opt" role="option" id="listbox-o-priya" aria-selected="false">
      <span class="ap-listbox__box" aria-hidden="true"><svg viewBox="0 0 24 24" focusable="false"><path d="m6 12.5 4 4 8-9"/></svg></span>
      <span class="ap-listbox__avatar" aria-hidden="true">PN</span>
      <span class="ap-listbox__who"><span class="ap-listbox__person">Priya Nair</span> <span class="ap-listbox__team">Support</span></span>
      <svg class="ap-listbox__tick" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg>
    </li>
    <li class="ap-listbox__opt" role="option" id="listbox-o-ravi" aria-selected="false">
      <span class="ap-listbox__box" aria-hidden="true"><svg viewBox="0 0 24 24" focusable="false"><path d="m6 12.5 4 4 8-9"/></svg></span>
      <span class="ap-listbox__avatar" aria-hidden="true">RK</span>
      <span class="ap-listbox__who"><span class="ap-listbox__person">Ravi Kumar</span> <span class="ap-listbox__team">Engineering</span></span>
      <svg class="ap-listbox__tick" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg>
    </li>
    <li class="ap-listbox__opt" role="option" id="listbox-o-vikram" aria-selected="false">
      <span class="ap-listbox__box" aria-hidden="true"><svg viewBox="0 0 24 24" focusable="false"><path d="m6 12.5 4 4 8-9"/></svg></span>
      <span class="ap-listbox__avatar" aria-hidden="true">VD</span>
      <span class="ap-listbox__who"><span class="ap-listbox__person">Vikram Das</span> <span class="ap-listbox__team">Research</span></span>
      <svg class="ap-listbox__tick" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg>
    </li>
  </ul>
</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

    The list is labelled by its visible name, and each person is an option, so the structure and the count of options are exposed.

  • 1.4.1 Use of Color Level A

    Selected people show a check mark and heavier text; in Select several each one has a ticked box, so selection never rests on the tint.

  • 2.1.1 Keyboard Level A

    Moving, choosing, jumping by typing and selecting a range all work from the keyboard.

  • 2.4.7 Focus Visible Level AA

    The list shows a focus ring, and the person in focus has a ring of their own, so it is clear where the arrow keys are.

  • 4.1.2 Name, Role, Value Level A

    role=listbox, role=option and aria-selected expose each option and its state; aria-multiselectable says when several can be chosen.

  • 4.1.3 Status Messages Level AA

    The number selected is a polite status, read after the list changes in Select several.

Usage

When to use it

Use it

  • Choosing from a list of people, files or items that people scan rather than type, and that needs more than plain text in each row.
  • Picking several items from a list of up to about twenty.

Use something else

  • A plain choice inside a form: a native select works everywhere with no script.
  • A list long enough that people would rather type: use a combobox.
  • Rows with buttons or links inside them: a listbox option cannot hold controls; use a grid or a list of buttons.

Common failures

How it usually goes wrong

  • Every option in the Tab order

    Ten options with tabindex=0 mean ten Tab presses to get past the list. One stop for the list, with arrow keys inside it, is the expected model.

  • Focus moved with no indicator

    With aria-activedescendant the browser does not draw a ring on the option, so the page has to. The option in focus here has its own ring.

  • Selected and focused drawn the same

    In Select several the option in focus is often not selected. A ring for focus and a check for selection keep the two apart.

  • No way to select several from the keyboard

    A list that only adds to its selection with Ctrl and a click leaves keyboard users with one choice. Space, Shift with the arrow keys and Ctrl+A cover it.

  • Interactive content inside options

    Options are read as plain text, so a button inside one cannot be found or pressed by a screen reader user. Keep options to text and decoration.

Notes

Building it

  • aria-activedescendant keeps focus on the list and points at an option by id, so options need unique ids and the script has to scroll the active one into view.
  • In a single-choice list, the selection follows focus. In Select several it does not: the arrow keys only move, and Space or Shift does the selecting.
  • The count is written only when it changes, so moving through a single-choice list does not repeat "1 selected".
  • Typing to jump compares the visible text, so it keeps working when the page is translated.

Sources: WAI-ARIA Authoring Practices: Listbox

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