Listbox and keyboard navigation
Associated themes :- Component
Listbox and keyboard navigation
Introduction
For this article, we start from the example of the listbox with checkboxes. The objective is to improve keyboard navigation in order to get close to the W3C / WAI guidelines.
ARIA patterns
The W3C maintains online specifications that describe how ARIA components should behave: WAI-ARIA Authoring Practices 1.1.
The specifications for the listbox tell us that:
- The list must be focusable (
tabindex="0"
). - The up / down arrows must be used to choose the selected item.
- We must be able to select an item by quickly typing the first few characters (e.g. when pressing the “p” and “h” keys, the selection must move to “Photos”).
- Shift + F10 must allow to open the focused item context menu if the item has one.
- In case of a multiple-selection list, the Ctrl + a shortcut should select all items.
- The Home and End keys must be able to respectively select the first and the last item of the list.
Example
Implementation
The functionality to automatically select the item by typing the first letters is not easy to implement. The following example uses XPath.
The first thing to do is listen to the keyboard once the focus is on the list. Depending on the key, it performs an action (selecting an item, moving the selected item…).
… // Init const listbox = document.querySelector("[role=listbox]"); // On keydown listbox.onkeydown = function(e) { var currentItem = this.querySelector("[aria-selected=true]"); switch (e.keyCode) { case 9: // TAB break; case 36: // home … e.preventDefault(); break; case 35: // end … e.preventDefault(); break; case 38: // Up arrow … e.preventDefault(); break; …
Other keys that are not used to perform an action on the list, i.e. letters and digits, are saved to create the search string. When the user does not type anything for a few milliseconds (500 in our example), we look for a list item that begins with the typed string and select it.
… case 65: // Ctrl + A if (e.ctrlKey) { … } default: // Search item starts with // Cancel current timer clearTimeout(timer); // Create search string searchString += e.key; var self = this; // Set a timer to search item after 500ms timer = setTimeout(function(){ // Search item var xpath = "li/span[starts-with(translate(text(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), '" + searchString + "')]"; var matchingElement = document.evaluate(xpath, self, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; // Reset search string searchString = ""; // If an item is found… if (matchingElement) { currentItem.setAttribute("aria-selected", "false"); matchingElement.parentElement.setAttribute("aria-selected", "true"); matchingElement.parentElement.focus(); matchingElement.parentElement.classList.add("active"); } }, 500); e.preventDefault();
To search the item in the list, we use the following XPath query
/li/span[starts-with(text(), "the string to search")]
. In addition, in order to fix the character case issue, we use thetranslate
function.Finally, for compatibility issues with Internet Explorer, we use the Google XPath polyfill, you just need to include it and install it at page load using:
wgxpath.install();
. - The list must be focusable (