Listbox

A listbox allows users to select one or more items from a list of items. There are two main variants of listboxes: single-select and multi-select. Single-select listboxes allow users to select one item from a list of items. Multi-select listboxes allow users to select multiple items from a list of items.

Screen readers are unable to present any semantic information about listbox options, as such listboxes cannot be used to present lists of interactive elements.

Role

A listbox container element must have a role of listbox.

Accessibility label

A listbox container element must have an accessibility label unless it is part of another widget such as a combobox. The accessibility label is the text that is read by screen readers to describe the element. For a listbox with a visible label, the accessibility label should be provided by an aria-labelledby attribute that points to the visible label. If the listbox does not have a visible label, or if for some reason the visible label is not a suitable accessibility label, then the accessibility label must be provided by an aria-label attribute.

Listbox has options

A listbox container element must have one or more child elements with a role of option. option elements may be contained directly within the listbox container element or within group elements with a role of group. Any option group must have at least one option element.

A listbox may not contain any non-presentational child elements with a role other than option or group. Additionally, the listbox, its option groups and its options may not contain any interactive elements such as buttons or links.

Options and groups accessibility labels

Each option or group element in the listbox must have an accessible name.

The accessibility label for an option element will usually be the text content of the element. If the option element has an aria-label attribute, the value of the attribute will be used as the accessibility label instead of the text content.

The accessibility label for a group element must be provided by an aria-label attribute or an aria-labelledby attribute that points to a visible label for that group.

1<!-- A simple listbox -->
2<div role="listbox" aria-label="Fruit">
3 <div role="option">Apple</div>
4 <div role="option">Banana</div>
5 <div role="option">Orange</div>
6</div>
7
8<!-- A listbox with option groups -->
9<div role="listbox" aria-label="Fruit">
10 <div role="group" aria-labelledby="apples">
11 <div id="apples" role="presentation">Apples</div>
12 <div role="option">Braeburn</div>
13 <div role="option">Cox</div>
14 <div role="option">Granny Smith</div>
15 </div>
16 <div role="group" aria-labelledby="bananas">
17 <div id="bananas" role="presentation">Bananas</div>
18 <div role="option">Cavendish</div>
19 <div role="option">Lady Finger</div>
20 </div>
21 <div role="group" aria-labelledby="oranges">
22 <div id="oranges" role="presentation">Oranges</div>
23 <div role="option">Blood Orange</div>
24 <div role="option">Navel</div>
25 <div role="option">Seville</div>
26 </div>
27</div>

Set size

Optionally, the aria-setsize attribute may be used to indicate the number of options in the listbox. If the aria-setsize attribute is used, then it should be set to the number of options in the listbox. The aria-setsize attribute must be set on the options themselves, not the container and it should be consistent across all options in the listbox.

Set position

Optionally, the aria-posinset attribute may be used to indicate the position of an option in the listbox. If the aria-posinset attribute is used, then it should be set to the position of the option in the listbox.

ARIA owns

If the disclosure buttons cannot be implemented as descendants of the listbox container, the aria-owns attribute can be used to indicate that the listbox owns the disclosures. However, since the aria-owns attribute is not supported by some assistive technologies, it is best practice to avoid it. If it must be used, the aria-owns attribute should be set on the listbox container and should point to the IDs of the disclosure buttons. Any elements referenced by the aria-owns attribute should have a role of option or group.

Selected options

Semantic HTML options will automatically set the selected property on the option that is selected. In non-semantic HTML listboxes, the aria-selected="true" attribute or aria-checked="true" must be set on the selected option or options. aria-selected="false" or aria-checked="false" may be set on all other options but is not requred since false is the default value for both attributes.

If the listbox is single-select, then only one option may be selected at a time. If the listbox is multi-select, then multiple options may be selected at a time. In that case the listbox container element must have a aria-multiselectable="true" attribute, or if the listbox container is a native HTML select element, then the multiple attribute is required.

1<!-- A simple listbox -->
2<div role="listbox" aria-label="Fruit">
3 <div role="option" aria-selected="true">Apple</div>
4 <div role="option">Banana</div>
5 <div role="option">Orange</div>
6</div>
7
8<!-- A multi-select listbox -->
9<div role="listbox" aria-label="Fruit" aria-multiselectable="true">
10 <div role="option" aria-selected="true">Apple</div>
11 <div role="option" aria-selected="true">Banana</div>
12 <div role="option">Orange</div>
13</div>

Focus sequence

The listbox container element must be included in the page's focus sequence unless the listbox is scrollable.

When focus is on the listbox container element, the aria-activedescendant attributes must point to the id of the first selected option, or the first option if no options are selected. The option indicated by the aria-activedescendent should show a visual indication that it is focused.

Scrollable listboxes should not be included in the page's focus sequence. Instead, focus should be placed on the first selected option, or the first option if no options are selected.

Under no circumstances should both the listbox container element and its options be included in the focus sequence.

1<!-- A simple listbox -->
2<div role="listbox" aria-label="Fruit" tabindex="0" aria-activedescendant="apples">
3 <div id="apples" role="option" aria-selected="true">Apple</div>
4 <div id="bananas" role="option">Banana</div>
5 <div id="oranges" role="option">Orange</div>
6</div>
7
8<!-- A scrollable listbox -->
9<div role="listbox" aria-label="Fruit">
10 <div id="apples" role="option" aria-selected="true" tabindex="0">Apple</div>
11 <div id="bananas" role="option">Banana</div>
12 <div id="oranges" role="option">Orange</div>
13</div>

Keyboard navigation

For clarity, when the focus moves to an option as described in this section, that should generally be achieved by having the aria-activedescendant attribute point to the id of that option. The browser focus should remain on the listbox container throughout. However, if the listbox is scrollable, then it is permissible to directly focus the option element as long as the listbox container itself is not focusable.

All vertically oriented listboxes must support the following keyboard navigation:

  • ArrowDown moves focus to the next option in the listbox. If focus is on the last option, move focus to the first option.
  • ArrowUp moves focus to the previous option in the listbox. If focus is on the first option, move focus to the last option.

All horizontally oriented listboxes must support the following keyboard navigation:

  • ArrowRight moves focus to the next option in the listbox. If focus is on the last option, move focus to the first option.
  • ArrowLeft moves focus to the previous option in the listbox. If focus is on the first option, move focus to the last option.

Both vertically and horizontally oriented listboxes may optionally support the following keyboard navigation:

  • Home moves focus to the first option in the listbox.
  • End moves focus to the last option in the listbox.

Single-select listboxes

In single-select listboxes the selection may optionally be changed with the focus when the arrow keys are pressed.

Alternatively the selection may be changed to the currently focused option when the Space key is pressed.

1// Example of changing the selection with the focus
2listbox.addEventListener("keydown", (event) => {
3 if (event.key === "ArrowDown" || event.key === "ArrowUp") {
4 const currentOption = listbox.querySelector('[aria-selected="true"]');
5 const newOption =
6 event.key === "ArrowDown"
7 ? currentOption.nextElementSibling
8 : currentOption.previousElementSibling;
9 if (newOption) {
10 event.preventDefault();
11 listbox.setAttribute("aria-activedescendant", newOption.id);
12 currentOption.setAttribute("aria-selected", "false");
13 newOption.setAttribute("aria-selected", "true");
14 }
15 }
16});
17
18// Example of changing the selection with the space key, arrow keys only change focus
19listbox.addEventListener("keydown", (event) => {
20 if (event.key === "ArrowDown" || event.key === "ArrowUp") {
21 const currentOption = document.getElementById(
22 listbox.getAttribute("aria-activedescendant")
23 );
24 const newOption =
25 event.key === "ArrowDown"
26 ? currentOption.nextElementSibling
27 : currentOption.previousElementSibling;
28 if (newOption) {
29 event.preventDefault();
30 listbox.setAttribute("aria-activedescendant", newOption.id);
31 }
32 }
33});
34listbox.addEventListener("keyup", (event) => {
35 if (event.key === "Space") {
36 event.preventDefault();
37 const focusedOption = document.getElementById(
38 listbox.getAttribute("aria-activedescendant")
39 );
40 const selectedOption = listbox.querySelector('[aria-selected="true"]');
41 if (focusedOption !== selectedOption) {
42 selectedOption.setAttribute("aria-selected", "false");
43 focusedOption.setAttribute("aria-selected", "true");
44 } else {
45 focusedOption.setAttribute("aria-selected", "false");
46 }
47 }
48});

Multi-select listboxes

Multi-select listboxes allow for several options to be selected, see Selected options. These listboxes may support one of two modes of keyboard interaction.

Mode 1 (Recommended)

This mode is recommended since it doesn't require the user to hold a modifier key such as Shift or Ctrl in order to select multiple options.

  • Space toggles the selected state of the current option.
  • Shift + ArrowDown (optional) moves the focus to the next option and toggles its selected state.
  • Shift + ArrowUp (optional) moves the focus to the previous option and toggles its selected state.
  • Shift + Space (optional) selects all options between the current option and the last select option.
  • Ctrl + Shift + Home (optional) selects all options from the first option to the current option. Optionally, the focus may move to the first option.
  • Ctrl + Shift + End (optional) selects all options from the current option to the last option. Optionally, the focus may move to the last option.
  • Ctrl + A (optional) selects all options in the listbox. Optionally, may unselect all options if all options are already selected.
1// Minimal implementation example, required interactions only
2listbox.addEventListener("keydown", (event) => {
3 if (event.key === "ArrowDown" || event.key === "ArrowUp") {
4 const currentOption = document.getElementById(
5 listbox.getAttribute("aria-activedescendant")
6 );
7 const newOption =
8 event.key === "ArrowDown"
9 ? currentOption.nextElementSibling
10 : currentOption.previousElementSibling;
11 if (newOption) {
12 event.preventDefault();
13 listbox.setAttribute("aria-activedescendant", newOption.id);
14 }
15 }
16});
17listbox.addEventListener("keyup", (event) => {
18 if (event.key === "Space") {
19 event.preventDefault();
20 const focusedOption = document.getElementById(
21 listbox.getAttribute("aria-activedescendant")
22 );
23 focusedOption.setAttribute(
24 "aria-selected",
25 focusedOption.getAttribute("aria-selected") === "true"
26 ? "false"
27 : "true"
28 );
29 }
30});

Mode 2

In this mode, moving focus with the arrow key, without holding down a modifier key such as Shift or Ctrl, will unselect all options except the focused option. The focused option will be selected. This mode is not recommended and therefore not currently supported as part of the Listbox pattern in the Evinced Unit Tester SDK.

  • Shift + ArrowDown moves the focus to the next option and toggles its selected state.
  • Shift + ArrowUp moves the focus to the previous option and toggles its selected state.
  • Ctrl + ArrowDown moves the focus to the next option without changing its selected state.
  • Ctrl + ArrowUp moves the focus to the previous option without changing its selected state.
  • Ctrl + Space toggles the selected state of the current option.
  • Shift + Space (optional) selects all options between the current option and the last select option.
  • Ctrl + Shift + Home (optional) selects all options from the first option to the current option. Optionally, the focus may move to the first option.
  • Ctrl + Shift + End (optional) selects all options from the current option to the last option. Optionally, the focus may move to the last option.
  • Ctrl + A (optional) selects all options in the listbox. Optionally, may unselect all options if all options are already selected.
1// Minimal implementation example, required interactions only
2listbox.addEventListener("keydown", (event) => {
3 if (event.key === "ArrowDown" || event.key === "ArrowUp") {
4 const currentOption = document.getElementById(
5 listbox.getAttribute("aria-activedescendant")
6 );
7 const newOption =
8 event.key === "ArrowDown"
9 ? currentOption.nextElementSibling
10 : currentOption.previousElementSibling;
11 if (newOption) {
12 event.preventDefault();
13 listbox.setAttribute("aria-activedescendant", newOption.id);
14 if (event.shiftKey) {
15 newOption.setAttribute(
16 "aria-selected",
17 newOption.getAttribute("aria-selected") === "true"
18 ? "false"
19 : "true"
20 );
21 } else if (!event.ctrlKey) {
22 const selectedOptions = listbox.querySelectorAll(
23 '[aria-selected="true"]'
24 );
25 selectedOptions.forEach((option) =>
26 option.setAttribute("aria-selected", "false")
27 );
28 newOption.setAttribute("aria-selected", "true");
29 }
30 }
31 }
32});
33listbox.addEventListener("keyup", (event) => {
34 if (event.key === "Space" && event.ctrlKey) {
35 event.preventDefault();
36 const focusedOption = document.getElementById(
37 listbox.getAttribute("aria-activedescendant")
38 );
39 focusedOption.setAttribute(
40 "aria-selected",
41 focusedOption.getAttribute("aria-selected") === "true"
42 ? "false"
43 : "true"
44 );
45 }
46});

Summary of tests

NameConformance LevelWCAG Success Criteria
RoleA4.1.2 Name, Role, Value
Accessibility labelA4.1.2 Name, Role, Value
Listbox has optionsA4.1.2 Name, Role, Value, 1.3.1 Info and Relationships
Optgroup has optionsA4.1.2 Name, Role, Value, 1.3.1 Info and Relationships
Unexpected rolesA4.1.2 Name, Role, Value, 1.3.1 Info and Relationships
Forbidden interactive childrenA4.1.2 Name, Role, Value, 1.3.1 Info and Relationships
Options accessibility labelsA4.1.2 Name, Role, Value
Set sizeA1.3.1 Info and Relationships
Set positionA1.3.1 Info and Relationships
ARIA ownsA1.3.1 Info and Relationships
Selected optionsA4.1.2 Name, Role, Value
Focus sequenceA2.1.1 Keyboard ,2.4.3 Focus Order
Keyboard navigationA2.1.1 Keyboard
Multi-select listboxesA2.1.1 Keyboard