Forms & Controls
Accessible Listbox (single & multi-select)
A list of options a user can select one or more values from.
Last verified against WCAG 2.2 and WAI-ARIA APG 1.2 on 2026-07-02.
Implementation
Sort by
- Relevance
- Newest first
- Oldest first
- Price: low to high
- Price: high to low
Languages (multi-select)
- JavaScript
- TypeScript
- Python
- Rust
- Go
- Swift
- Kotlin
2 of 7 selected
<span id="sort-label">Sort by</span>
<!-- role="listbox" wraps role="option" items. Single-select: the chosen
option has aria-selected="true" and is the only Tab stop (roving
tabindex); the rest are tabindex="-1". -->
<ul role="listbox" aria-labelledby="sort-label">
<li role="option" aria-selected="true" tabindex="0">Relevance</li>
<li role="option" aria-selected="false" tabindex="-1">Newest</li>
<li role="option" aria-selected="false" tabindex="-1">Price: low to high</li>
<li role="option" aria-selected="false" tabindex="-1">Price: high to low</li>
</ul>Required roles, states & properties
| Element | Attribute | Why |
|---|---|---|
| List container | role="listbox" | Identifies the element as a listbox so AT announces it as a selectable list, not a generic group of text. |
| List container | aria-label / aria-labelledby | Gives the listbox an accessible name so it's announced as e.g. "Sort by, listbox" rather than an unnamed list. |
| List container (multi-select only) | aria-multiselectable="true" | Tells AT more than one option may be selected at once, changing how selection state is announced. |
| Each option | role="option" | Identifies each row as a selectable option within the listbox. |
| Each option | aria-selected | Communicates each option's individual selected/not-selected state, required on every option, including unselected ones. |
| Each option | tabIndex (roving) | Only the active option has tabIndex=0 so Tab moves past the whole widget in one stop; arrow keys move the roving cursor among options with tabIndex=-1. |
Keyboard interaction model
| Key | Behavior |
|---|---|
| Down Arrow | Single-select: moves focus to the next option AND selects it. Multi-select: moves focus only, selection unchanged. |
| Up Arrow | Single-select: moves focus to the previous option AND selects it. Multi-select: moves focus only. |
| Space | Multi-select only: toggles the focused option's selected state. |
| Shift + Down/Up | Multi-select only: extends a contiguous selection range from the last toggled option to the newly focused option. |
| Ctrl/Cmd + A | Multi-select only: selects all options (simplified in this demo's key handler; document the full toggle-all behavior in production). |
| Home / End | Moves focus (and, in single-select, selection) to the first / last option. |
Focus management rules
- Only one option is in the Tab order at a time (roving tabindex); Tab moves straight past the whole listbox.
- Single-select: moving the roving-tabindex cursor changes selection immediately (selection follows focus).
- Multi-select: moving the roving-tabindex cursor never changes selection by itself, only Space or Shift+Arrow does.
- Clicking an option moves both DOM focus and the roving-tabindex cursor to that option.
WCAG 2.2 success criteria mapping
| SC | Name | Level | Why it applies |
|---|---|---|---|
| 1.3.1 | Info and Relationships | A | Structure and relationships conveyed visually are also programmatically determinable. |
| 2.1.1 | Keyboard | A | All functionality is operable through a keyboard interface with no specific timing. |
| 4.1.2 | Name, Role, Value | A | For all UI components, name, role, and value are programmatically determinable; states and changes are announced. |