Standards · ARIA

Role Composite widget

listbox

Marks an element as a list of selectable options. Use <select> for the simple single-select case — it gives you a mobile-friendly picker and full keyboard support. Reach for role="listbox" when you need custom styling, multi-select with custom rendering, or pairing with a combobox.

When to use

Use <select> (single-select) or <select multiple> whenever possible. Native delivers the entire interaction model, including platform-correct keyboard handling.

role="listbox" is appropriate when:

  • The listbox is the popup of a custom combobox.
  • You need rich option content (icons, secondary text) that <select> cannot render.
  • You need multi-select with custom checkmark styling.
  • You’re building a transfer list or filterable picker.

Children must be role="option" (with role="group" allowed as an intermediate wrapper for sectioning). Set aria-multiselectable="true" for multi-select listboxes.

Keyboard + focus contract

Per the APG listbox pattern:

  • Tab moves focus to the listbox; Tab moves out. Inside the listbox, focus stays on the container and the “active” option is tracked via aria-activedescendant.
  • Up/Down arrows move the active option.
  • Home / End jump to first / last.
  • Single-select: each arrow press also selects the active option.
  • Multi-select: Space toggles selection on the active option; Shift+Arrow extends a contiguous range; Ctrl/Cmd+A selects all.
  • Typeahead: typing a letter jumps to the next option starting with that letter.

Common failures

  • Moving DOM focus to each option instead of using aria-activedescendant. Breaks typeahead and combobox integration.
  • role="option" children without id attributes — aria-activedescendant cannot reference them.
  • Multi-select listbox missing aria-multiselectable="true". Screen readers announce it as single-select.
  • Listbox owns options that live elsewhere in the DOM without using aria-owns to make the relationship explicit.
  • Using role="listbox" for a static list of read-only items. That’s role="list", not listbox — listbox implies interactivity.

Example

<label id="topicsLabel">Pick your topics</label>
<ul
  role="listbox"
  aria-labelledby="topicsLabel"
  aria-multiselectable="true"
  tabindex="0"
  aria-activedescendant="topic-1"
>
  <li id="topic-1" role="option" aria-selected="true">Accessibility</li>
  <li id="topic-2" role="option" aria-selected="false">Assistive tech</li>
  <li id="topic-3" role="option" aria-selected="true">Standards</li>
</ul>