BPOW: Arrow keys for mega menu navigation

By September 18th, 2013

In this week’s Best Practice of the Week we’ll take a look at making mega menus easier to navigate for keyboard and screen reader users.

Mega menus are a great way to enhance a normal primary navigation since it allows for simultaneous visibility of all of a section’s links in one area and does not require the user to hunt through levels of menus. However, mega menus do have their pitfalls.

One of these pitfalls is the potentially massive number of links that a mega menu may include. While this is one of the benefits of mega menus, it also forces keyboard and screen reader users to navigate through a significant amount of links and content in order to find the item they’re looking for. One of the best things we can do to make navigation of mega menus easy for the user is to enhance traditional mouse and keyboard (tab-based) navigation with the use of arrow keys. Namely, we want to use the left and right arrow keys especially to allow users to navigate between top-level menu items in order to avoid the excessive keystrokes required to navigate through all mega menus in order to select the link they need.

The following table outlines suggested arrow key interactions that can be added to any mega menu via JavaScript in order to improve user experience.

Mega Menu State Event Interaction
Mega menu closed Keyboard focus on top level menu item Mega menu shown
Mega menu open Keyboard tabbing Focus moves through all elements in mega menu in source order
Mega menu open, focus on top level menu item Left or right arrow key pressed Focus moves to next/previous top level menu item
Mega menu open, focus on top level menu item Down arrow key pressed Focus moves to into mega menu, first column, primary menu item
Mega menu open, focus in mega menu on primary menu item Left or right arrow key pressed Focus moves to next/previous primary menu item (skip between columns)
Mega menu open, focus in mega menu on primary menu item Up arrow key pressed Focus moves up to top level menu item
Mega menu open, focus in mega menu on primary menu item Down arrow key pressed Focus moves down through column
Mega menu open, focus in mega menu on menu item in column Up or down arrow key pressed Focus moves up or down through column
Mega menu open, focus in mega menu on menu item in column Left or right arrow key pressed Focus moves left or right to next/previous primary menu item (skip between columns)

This JavaScript (jQuery-based) example code assumes a UL is used to mark up the top level navigation items. Additional code would be needed to bind arrow keydown events to elements within each sub/mega menu.


$('.top-level-link')
    .keydown(function(e){
        // Listen for the up, down, left and right arrow keys, otherwise, end here
        if ([37,38,39,40].indexOf(e.keyCode) == -1) {
            return;
        }

        // Store the reference to our top level link
        var link = $(this);

        switch(e.keyCode) {
            case 37: // left arrow
                // Make sure to stop event bubbling
                e.preventDefault();
                e.stopPropagation();

                // This is the first item in the top level mega menu list
                if(link.parent('li').prevAll('li').filter(':visible').first().length == 0) {
                    // Focus on the last item in the top level
                    link.parent('li').nextAll('li').filter(':visible').last().find('a').first().focus();
                } else {
                    // Focus on the previous item in the top level
                    link.parent('li').prevAll('li').filter(':visible').first().find('a').first().focus();
                }
                break;
            case 38: /// up arrow
                // Find the nested element that acts as the menu
                var dropdown = link.parent('li').find('.menu');

                // If there is a UL available, place focus on the first focusable element within
                if(dropdown.length > 0){
                    e.preventDefault();
                    e.stopPropagation();

                    dropdown.find('a, input[type="text"], button, etc.').filter(':visible').first().focus();
                }

                break;
            case 39: // right arrow
                // Make sure to stop event bubbling
                e.preventDefault();
                e.stopPropagation();

                // This is the last item
                if(link.parent('li').nextAll('li').filter(':visible').first().length == 0) {
                    // Focus on the first item in the top level
                    link.parent('li').prevAll('li').filter(':visible').last().find('a').first().focus();
                } else {
                    // Focus on the next item in the top level
                    link.parent('li').nextAll('li').filter(':visible').first().find('a').first().focus();
                }
                break;
            case 40: // down arrow
                // Find the nested element that acts as the menu
                var dropdown = link.parent('li').find('.menu');

                // If there is a UL available, place focus on the first focusable element within
                if(dropdown.length > 0){
                    // Make sure to stop event bubbling
                    e.preventDefault();
                    e.stopPropagation();

                    dropdown.find('a, input[type="text"], button, etc.').filter(':visible').first().focus();
                }
                break;
        }
    });

Tagged with: