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>78<!-- 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>78<!-- 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>78<!-- 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 focus2listbox.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.nextElementSibling8 : 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});1718// Example of changing the selection with the space key, arrow keys only change focus19listbox.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.nextElementSibling27 : 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 only2listbox.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.nextElementSibling10 : 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 only2listbox.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.nextElementSibling10 : 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});