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 withoutidattributes —aria-activedescendantcannot 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-ownsto make the relationship explicit. - Using
role="listbox"for a static list of read-only items. That’srole="list", notlistbox— 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>