A core requirement for developers using ARIA is to provide keyboard access for widgets, as users of screen readers rely on the keyboard to navigate web sites and applications. A large part of providing keyboard access is managing focus of a widget’s descendants (e.g., buttons in a toolbar, tabs in a tablist, menuitems in a menu, etc.), and the W3C guidelines provide two different approaches for doing so. This article explains both approaches and provides some practical advice for choosing between them.
As outlined in the ARIA specification and corresponding Best Practices document, providing keyboard access requires, in part, that each widget has one tab stop by default and is responsible for discreetly managing focus for its descendants. Following these guidelines enables users to quickly navigate a page or application by using the tab key to move between widgets. Once a user has tabbed into a widget, they can then use other keys (the arrow keys for example) to move focus amongst the widget’s descendants.
The Roaming TabIndex Technique requires each of a widget’s descendants be focusable, either through the use of natively focusable HTML elements, or by making an element focusable using the
tabIndex attribute. To use this technique, begin by setting the
tabIndex attribute of a widget’s first descendant to a value that is equal to or greater than 0. (A value of 0 will result in the tab order of the widget being relative to its position in the page. Use a value greater than 0 to precisely control a widget’s tab order.) Next, set the
tabIndex values, use a
keydown event handler to manage focus of the descendants once the widget is focused by the user. As the user presses the arrow keys, call the
focus method to activate the appropriate descendant and update the
tabIndex of the remaining descendants so that the currently focused element is the only one that is natively focusable.
The following menu button example illustrates how to use the Roaming TabIndex Technique to create a widget that is both keyboard and screen-reader accessible. To test this example yourself, you can use the free NVDA Screen Reader and Firefox 3. Alternatively, you can watch the screen cast of the example running in Firefox 3 using the NVDA screen reader.
Having studied the WAI-ARIA Best Practices document, as well as having used the Roaming TabIndex Technique in several widget implementations, I have distilled several best practices for using this approach:
tabIndexattribute to make non-focusable HTML elements focusable, for better backward compatibility in browsers that don’t support the
tabIndexattribute on all elements.
keydownevent when binding handlers used to manage focus since it is not possible to handle non-alphanumeric keys using the
keypressevent in IE.
tabIndexattribute, avoid using the
setAttributemethod, to prevent case-sensitivity mistakes in IE. Setting the
tabIndexattribute using the camel-case DOM property is both the most compact and most compatible syntax across browsers. For example:
myElement.tabIndex = -1;
tabIndexattribute of a widget’s descendants, set the attribute’s value to 0 before calling the
focusmethod to ensure that the element’s default focus outline is rendered in IE.
setTimeoutmethod and a
setTimeoutcan help screen readers keep up while the focus is being changed. I have found this to be true when testing on VoiceOver for the Mac. A
try/catchblock can help suppress unwanted XUL-related errors logged to the console when focusing elements in Firefox.
Unlike the Roaming TabIndex Technique, use of the
activedescendant property doesn’t require any of a widget’s descendants to be focusable. Instead, this technique requires only that the widget’s root element be made focusable via the
tabIndex attribute. (Note: this technique doesn’t work in the current version of Safari as it doesn’t support the
tabIndex attribute on all HTML elements.) Using this approach, the
activedescendant property is applied to the widget’s root element, and as the user presses the arrow keys, the value of the property is set to the id of the element that represents the user’s current selection. Since this approach doesn’t rely on focusing a widget’s descendants via the
focus method, the browser will not provide any default rendering of the user’s current selection. Therefore, when using the
activedescendant property the developer is responsible for rendering focus for the widget’s currently active descendant via CSS.
The following example adapts the previous example to illustrate how to use the
activedescendantExample Screen Cast
As illustrated in the screen cast, the
activedescendant property can provide a user experience that is identical to the Roaming TabIndex technique.
tabIndexattribute on a widget’s root element can result in a focus outline being drawn around the widget. For widgets with descendants, having a focus outline surround an entire control is both unfamiliar to the user (not something you’ll see on the desktop), as well as ugly. So when using the
activedescendantproperty, it is best to suppress the focus outline. This can be accomplished by setting the
outlineCSS property in Firefox and IE 8, and using the
hideFocusattribute for IE 6 and 7. Unfortunately it is not possible to suppress the focus outline in the current version of Opera.
Having evaluated both the Roaming TabIndex Technique and the use of the
activedescendant property, the Roaming TabIndex Technique is the better choice, because it is a solution that works “with the grain”. As such, it is more forward and backward compatible — especially when you are trying to support a wide array of browsers like we are at Yahoo!. Using the
activedescendant property requires more effort for less overall benefit and compatibility. Here are the downsides to using the
tabIndexattribute on all elements. Currently this not supported in Safari.
clickevent accordingly. When using the Roaming TabIndex technique, developers can simply listen for the
activedescendant property, the Roaming TabIndex Technique allows widgets to be both keyboard accessible and screen-reader accessible in browsers that don’t support ARIA and don’t support the
tabIndex attribute on all elements. For example, if a widget’s descendants are built using the set of natively focusable HTML elements, users of screen readers will still perceive them as actionable/clickable elements. Consider the following screen cast of our first example running in IE 7 (a browser without ARIA support) using JAWS 10.
In this example, while the user doesn’t perceive the button’s menu as a menu, the screen reader does announce each button in the menu as it is focused — letting the user know that each item is actionable/clickable. Additionally, since the button’s menu is built using the natively focusable
<button> element, this widget will be keyboard accessible to all A-Grade browsers, not just those that support the
tabIndex attribute on all elements.
I suspect that the
activedescendant property was developed as an alternative to the Roaming TabIndex Technique in part because the
blur events don’t bubble like other DOM events. This was a problem since developers need to listen for these events in order to customize how focus is drawn in a way that works cross browser, and attaching individual
blur event handlers to each of a widget’s focusable descendants has consequences for performance — especially for large composite widgets like trees and menus. That said, since we now have an easy way of listening for
blur in a performance-conscious way, I feel like there are currently more downsides than upsides to using the