Forms & Input

Listbox

Available

Selecting from a list of options. Supports single and multi-select with arrow key navigation.

What is a Listbox?

A listbox is a UI that displays a list of options for users to choose from. While it may look like an ordinary list, it differs in that it represents a collection of selectable itemsand tracks which item is currently selected.

This page covers the single-select listbox. It works well when the number of options is small and can remain visible at all times. If the list needs to open and close, consider using a Combobox instead.

Why Does Accessibility Matter?

The solution is the role="listbox" > role="option" structure, combined with aria-selected and keyboard focus management(either roving tabindex or aria-activedescendant).

Live Demo (Recommended Implementation)

This is a listbox for choosing a single fruit. Focus the list, then try navigating with the arrow keys.

Accessible Listbox (Single Select)
Choose your favorite fruit
  • Apple
  • Orange
  • Grape
  • Peach
  • Strawberry
  • Melon

Try it: Tab to focus the list → ↑ ↓ to move the selection, Home / End to jump to the first or last item. You can also click to select.

Tip

This demo uses the aria-activedescendant approach. Focus stays on the list (<ul>), and only the attribute indicating the current option changes. You can also implement this with the "roving tabindex" approach, where tabindex is moved between individual options.

Keyboard Interaction

KeyActionPriority
TabMove focus to / away from the listboxRequired
/ Move to the next / previous option (selection updates with movement)Required
Home / EndMove to the first / last optionRecommended
Character keysMove to the next option starting with the typed character (rapid typing matches multiple characters)Recommended

Required WAI-ARIA Roles and Properties

TargetAttribute / RoleMeaning
Containerrole="listbox"Indicates a collection of selectable items.
Containeraria-labelledby (or aria-label)Provides an accessible name for the listbox.
Containertabindex="0" + aria-activedescendantPlaces focus on the list and points to the current option id (activedescendant pattern).
Each optionrole="option" + unique idA single option. The id is referenced by aria-activedescendant.
Each optionaria-selected="true | false"Selection state. Must be updated whenever the selection changes.

Implementation: Recommended Pattern (Good)

Good / Recommended

Place role="option" elements inside a role="listbox", and keep aria-selected and aria-activedescendant in sync with keyboard interaction.

Markup:

<span id="lb-label">Choose your favorite fruit</span>

<ul role="listbox"
    id="lb-list"
    tabindex="0"
    aria-labelledby="lb-label"
    aria-activedescendant="lb-opt-0">
  <li role="option" id="lb-opt-0" aria-selected="true">Apple</li>
  <li role="option" id="lb-opt-1" aria-selected="false">Orange</li>
  <li role="option" id="lb-opt-2" aria-selected="false">Grape</li>
</ul>

Keyboard interaction and selection sync:

const list = document.getElementById('lb-list');
const options = [...list.querySelectorAll('[role="option"]')];

const select = (index) => {
  options.forEach((opt, i) => {
    opt.setAttribute('aria-selected', String(i === index));
  });
  const active = options[index];
  list.setAttribute('aria-activedescendant', active.id); // Communicates which option is active
  active.scrollIntoView({ block: 'nearest' });
};

const current = () =>
  options.findIndex((o) => o.getAttribute('aria-selected') === 'true');

list.addEventListener('keydown', (e) => {
  const i = current();
  let next = null;
  if (e.key === 'ArrowDown') next = Math.min(i + 1, options.length - 1);
  if (e.key === 'ArrowUp')   next = Math.max(i - 1, 0);
  if (e.key === 'Home')      next = 0;
  if (e.key === 'End')       next = options.length - 1;
  if (next !== null) { e.preventDefault(); select(next); }
});

// Also select on click
options.forEach((opt, i) => opt.addEventListener('click', () => {
  select(i);
  list.focus();
}));

Anti-pattern (Bad)

Below is a list of <div> elements with only onclick handlers.You can click to select, but the list can't receive focus, arrow keys don't work, and the selection state is not communicated.

Broken listbox built with divs
Apple
Orange
Grape
Peach

Try it: Tab cannot focus the list, and arrow keys do nothing. Screen readers don't recognize it as a selectable list, and the selection state is not announced.

<!-- ❌ Anti-pattern: div list + onclick -->
<div class="list">
  <!-- No role, no aria-selected, completely inoperable via keyboard -->
  <div class="option" onclick="pick(this)">Apple</div>
  <div class="option" onclick="pick(this)">Orange</div>
  <div class="option" onclick="pick(this)">Grape</div>
</div>

Bad / Avoid

Problems with this implementation:

  • Not keyboard operablediv elements cannot receive focus, and arrow keys have no effect.
  • Role is not communicated — Without role="listbox"/role="option", it is not recognized as a selectable list.
  • Selection state is not communicated — Without aria-selected, there is no way to know which item is selected.
  • No label or position info — The group name and "X of Y" position are not announced.

Implementation Checklist


Source (English):Listbox Pattern — W3C APG(opens in a new tab)