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.
When it comes to managing focus, the WAI-ARIA Best Practices document provides two different techniques for developers: the Roaming TabIndex Technique and use of the activedescendant property.
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 attribute of all remaining descendants to -1. (A value of -1 removes an element from the default tab flow, while preserving its ability to be focused via JavaScript.) This ensures that all of a widget’s descendants are focusable via JavaScript, but only one is in the default tab flow of its containing page or application, and therefore focusable by the user.
With these 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.
Test Menu Roaming TabIndex Example
Menu Button Using Roaming TabIndex Technique @ Yahoo! Video
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:
tabIndex attribute to make non-focusable HTML elements focusable, for better backward compatibility in browsers that don’t support the tabIndex attribute on all elements.keydown event when binding handlers used to manage focus since it is not possible to handle non-alphanumeric keys using the keypress event in IE.tabIndex attribute, avoid using the setAttribute method, to prevent case-sensitivity mistakes in IE. Setting the tabIndex attribute using the camel-case DOM property is both the most compact and most compatible syntax across browsers. For example: myElement.tabIndex = -1;tabIndex attribute of a widget’s descendants, set the attribute’s value to 0 before calling the focus method to ensure that the element’s default focus outline is rendered in IE.setTimeout method and a try/catch block. Using setTimeout can 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/catch block can help suppress unwanted XUL-related errors logged to the console when focusing elements in Firefox.activedescendant PropertyUnlike 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.
activedescendant ExampleThe following example adapts the previous example to illustrate how to use the activedescendant property.
activedescendant Example Screen Cast
Menu Button Using The "activedescendant" Property @ Yahoo! Video
As illustrated in the screen cast, the activedescendant property can provide a user experience that is identical to the Roaming TabIndex technique.
activedescendant PropertytabIndex attribute 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 activedescendant property, it is best to suppress the focus outline. This can be accomplished by setting the outline CSS property in Firefox and IE 8, and using the hideFocus attribute 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 activedescendant property:
tabIndex attribute on all elements. Currently this not supported in Safari.
click event accordingly. When using the Roaming TabIndex technique, developers can simply listen for the click event.Unlike 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.
Screen reader accessible Menu Button @ Yahoo! Video
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 focus and 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 focus and 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 focus and blur in a performance-conscious way, I feel like there are currently more downsides than upsides to using the activedescendant property.
February 24, 2009 at 9:55 am
It’s funny, I posted working library as a suggestion for YUI3 three days ago – http://yuilibrary.com/projects/yui3/ticket/2526008
http://code.google.com/p/qfocuser/ – standalone class for keyboard navigable AJAX widgets
July 7, 2009 at 7:56 am
Yep, I strongly recommend roaming tabindex too. Nice post.
August 7, 2009 at 11:05 am
This article was very helpful. I have one point of general confusion. Clearly being keyboard accessible is good for both non mouse users and sight impaired users. The examples often point to having a one tab stop widget and then using arrow keys. However my testing so far with JAWS 10 shows that the browser never receives the arrow keys because JAWS treats them as special reading keys. So now my widget is no longer keyboard accessible.
Anyone have thoughts on this catch-22?
August 10, 2009 at 9:20 am
Hi John -
You are correct by default the virtual buffer is enabled in JAWS, meaning that the arrow keys are intercepted by the screen reader. This is the case for all screen readers. However, in the more recent versions of ARIA-enabled screen readers, the screen reader will automatically toggle the virtual buffer off by default when focus is given to an element with an ARIA role applied. And the last time I checked this was the behavior for JAWS 10 as well. If you are finding that not to be the case, perhaps you have your installation of JAWS configured differently.
- Todd
August 10, 2009 at 9:36 am
@Todd: actually, the behavior you are describing comes from NVDA, http://www.nvda-project.org, and not JAWS, http://www.freedomscientific.com, screen reader. At least I do not remember seeing JAWS toggle virtual buffer off when an ARIA-enabled widget receives focus. NVDA, however, does so for sure.
@David: the only way to force a screen reader to toggle virtual buffer is to place your widget inside a role of application. This is what we have done inside the “search assist” widget of the Yahoo! Search, http://www.ysearch.com.
January 28, 2010 at 3:32 am
[...] some great examples of how widgets should react to keyboard use, and Todd Kloots of Yahoo does a great job of explaining the techniques behind good keyboard usability (also as a video and using YUI3 and focusing on WAI-ARIA). Patrick Lauke of Opera also wrote a [...]
January 29, 2010 at 3:18 am
[...] Improving Accessibility Through Focus Management [...]
February 13, 2010 at 12:49 pm
[...] by using JavaScript tricks, you can make any element keyboard accessible. And if you build widgets, go even further by following the rules of keyboard navigation. You could [...]
February 18, 2010 at 3:48 am
[...] by using JavaScript tricks, you can make any element keyboard accessible. And if you build widgets, go even further by following the rules of keyboard navigation. You could [...]
June 18, 2010 at 1:07 am
[...] by using JavaScript tricks, you can make any element keyboard accessible. And if you build widgets, go even further by following the rules of keyboard navigation. You could [...]
September 2, 2010 at 9:06 pm
I must admit that I have been quite lax in making sure my sites are accessablet to keyboard only users. I should probably do something about this.