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:

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…).


    …
    // On keydown
    $("[role=listbox]").on("keydown", function (e) {            
        var currentItem = $(this).find("[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.attr("aria-selected", "false");
                $(matchingElement).parent().attr("aria-selected", "true").focus().addClass("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 the translate 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();.