I would like to create a menu for my Web app. I know the HTML markup to create a list, though I’m not sure about the ARIA semantics or the Javscript code.
This problem has two parts. 1) choosing the right ARIA semantics, and 2) defining the keyboard behavior via Javascript. First, I’d like to get the semantics right before adding behavior, so I’ll start with ARIA.
I know with ARIA its possible to markup a menu list with role=”list” or role=”menu”. Menu is a more specialized type of list and intended for use with a series of user activatable options. Since I’m working with a menu list, choosing an ARIA menu seams a simple choice. Below is a quick description of the menu role and related menuitem role.
role=”menu” Define a container of options a user can activate
role=”menuitem” Define each sub menu item option contained within a menu role
The above semantics are quite straight forward, add a role=”menu” to a list container, and then for each sub list item, add a role=”menuitem”. Below are the HTML semantics I’ve come up with for my menu.
<h3 id="a_menu" class="offscreen">A Menu Example</h3> <ol role="menu" tabindex=0 aria-label="a_menu"> <li role="menuitem" tabindex="-1">option1</li> <li role="menuitem" tabindex="-1">option2</li> <li role="menuitem" tabindex="-1">option3</li> <ol> note: tabindex is not valid HTML4 but is valid HTML5
The above code as expected informs an AT that the list should be treated as a menu along with the sub list elements. A tabindex was added as well and that has more to do with the Javascript behavior. I chose to use an ordered list (<OL>) over an unordered list (<OL>) because the options in my menu are ordered and the sequence is prioritized.
For information on aria-label see a previous post label but which label. The idea here was that the layout and position of the menu would probably give enough information for a cited user to figure it out, while a user accessing the menu via an AT would need some sort of menu description.
The tabindex attribute was added to allow menu elements to have focus and is the real magic behind this menu. Without a tabindex, only HTML elements such as an input field or link can receive focus via tabing to them. A tabindex can have one of three possible values: 0, -1, or a positive integer. When a tabindex has a value of 0, this element is put in the normal tab order sequence. When a tabindex has a positive integer value this allows the normal flow of the tabindex to follow the integer sequence. When a tabindex has a -1 value, the element is not in the tab sequence but can be focused via Javascript. I tried writing a few examples on my own and it all made sense (eventually
). Also see the related ARIA spec http://www.w3.org/TR/wai-aria-primer/#focus for more info on the tabindex.
The menu container was given a tabindex=0 so a user can tab to the menu and it be treated as a main navigation element in the normal flow tab order flow. The sub menu items were given a tabindex=”-1″ so they would stay out of the tab natural order sequence and the user would not have to always tab through each sub list option to move on in the document navigation (which would get annoy quick). More importantly the tabindex=”-1″ allows each sub option to receive focus and is the basis of my keyboard navigation.
In my case, I’d like a user to be able to focus the menu and then key up and down through the options and allow the him/her to hit the enter to activate a selection. Below is a JQuery Javascript snippet accomplishing this.
...
//put ascii keycodes into a variable for convience
var keymap = {
'up': 38,
'down': 40,
'enter': 13
};
...
$('ol#a11ymenu').bind('keydown', function(e) {
switch(e.keyCode) {
case keymap.down:
if ($(this).find('li.hasfocus').is(':last-child')) {
return false; //end of list, do nothing
};
//give the next list option focus and add the class marker to it
$(this).find('li.hasfocus').removeClass('hasfocus').next().
addClass('hasfocus').focus();
break;
case keymap.up:
if ($(this).find('li.hasfocus').is(':first-child')) {
return false; //start of list, do nothing
};
//give the previous list option focus and add the class marker to it
$(this).find('li.hasfocus').removeClass('hasfocus').prev().
addClass('hasfocus').focus();
break;
case keymap.enter:
//activate the related list option and do something like show a tab
reallyExcitingAction($(e.target));
break;
}
});
...
A lot is happening in the above code. The code comments should help to explain it but I’m only going to explain the general idea of whats happening. When a user tabs to the menu list, s/he can then navigate to sub menu options by keying either the UP or DOWN arrow key. In the case of the DOWN arrow key, focus is given to the next menu option. In the case of the UP arrow key, focus is given to the previous option. In the case of the ENTER key being hit, some predefined behavior should be called. In my case “reallyExcitingAction” is called which is too exciting to put in this wee post.
Also worth noting, I used a CSS class to keep track of which menu item has focus. Its not possible (or at least easy) using javascript to determine which DOM element has focus, so a developer has to either use a variable or some other method to keep track of which HTML element has focus. I found using a variable unnecessary work, and using a CSS class, “hasfocus”, as a very effective method. For one, using a CSS class is very reliable and works well with JQuery selectors to find the “hasfocus” class. When using a variable with Javascript I had to maintain the variable state which I found tricky and unreliable. And two, adding style to the currently focussed element is as easy as defining the CSS style. This point may seem minor but I found it very important to give a visual indicator to the user of which option currently had focus.
I think/hope that covers most of the important stuff.
By using ARIA semantics I was able to describe my menu to an AT and also add keyboard behavior to the menu via javascript so users without a mouse can still access it via the keyboard. This was accomplished using the ARIA roles “menu” and “menuitem”, as well as a tabindex and class identifier. This menu solution could have taken many other forms. For me though, the described solution has worked very well and with minimal effort.
But as mentioned a variable could be used to keep track of focus like in this Mozilla tree example. I also highly recommend taking a look at the CodeTalks wiki on adding keyboard navigation which covers this topic in detail.