The problem: today's DHTML pages lack keyboard accessibility
An increasing number of web applications are using JavaScript to mimic desktop widgets like menus, tree views, rich text fields, and tab panels. Web developers are constantly innovating, and future applications will contain complex, interactive elements such as spreadsheets, calendars, organizational charts, and beyond. Until now, web developers wanting to make their styled <div>
and <span>
based widgets keyboard accessible have lacked the proper techniques. However, keyboard accessibility is part of the minimum accessibility requirements that any web developer should be aware of.
Here's a real example: most DHTML menus don't act like regular menus with respect to keyboard access. If you can use the keyboard to get to the menu at all, a common mistake is to put each menu item in the tab order (often accomplished by making each menu item an <a>
element). In fact, the correct behavior for menus is that the entire menu should be in the tab order once, and arrow key navigation should be supported to move from menu item to menu item. This also true for other "grouped navigation" widgets such as tree views, grids, and tab panels.
It's now possible for HTML authors to do the right thing. Making these widgets compatible with assistive technologies is documented under Accessible DHTML.
The solution: changes to standard behavior of tabIndex
Firefox 1.5 follows Microsoft Internet Explorer's lead by extending the tabIndex
attribute to allow any element's focusability to be altered. By following the IE system for tabIndex
we're allowing DHTML widgets which are already keyboard accessible in IE to also be keyboard accessible in Firefox 1.5. The rules have had to be bent in order to allow authors to make custom widgets keyboard accessible.
The following table describes the new tabIndex
behavior:
tabIndex attribute |
Focusable with mouse or JavaScript via element.focus() |
Tab navigable |
---|---|---|
not present | Follows the default behavior of element (yes for form controls, links, etc.). | Follows default behavior of element. |
Negative (e.g. tabIndex="-1" ) |
Yes | No, author must focus it with element.focus() as a result of arrow or other key presses. |
Zero (e.g. tabIndex="0" ) |
Yes | In tab order relative to element's position in document. |
Positive (e.g. tabIndex="33" ) |
Yes | tabIndex value manually changes where this element is positioned in the tab order. These elements will be positioned in the tab order before elements that have tabIndex="0" or that are naturally tabbable. |
How to use the new system
To make simple tab navigable widgets, the solution is to use tabIndex="0"
on the <div>
or <span>
representing it. Here's an example of a span-based checkbox that is keyboard accessible in both Firefox 1.5 and IE (although the :before
rule for the checkbox image doesn't work in IE).
For grouping widgets -- such as menus, tab panels, grids, or tree views -- the parent element should have tabIndex="0"
, and each decendent choice/tab/cell/row should have tabIndex="-1"
. A keydown event that watches for arrow keys can then use element.focus()
to set the focus on the appropriate decendent widget and style it so that it appears focused. Here's an example of a DHTML tree view that is keyboard and screen reader accessible in Firefox nightlies (we're still finishing the work to make it work in IE).
Keep in mind that this is not yet part of any W3C or other official standard. At this time it is necessary to bend the rules in order to have full keyboard accessibility.
Authoring tips
Use onfocus
to track the current focus
The events onfocus
and onblur
can now be used with every element. There is no standard DOM interface to get the current document focus, so if you want to track that you'll have to keep track of it in a JavaScript variable.
Don't assume that all focus changes will come via key and mouse events, because assistive technologies such as screen readers can set the focus to any focusable element, and that needs to be handled elegantly by the JavaScript widget.
Dynamically change focusability using the tabIndex
property
You may want to do this if a custom control becomes disabled or enabled. Disabled controls should not be in the tab order. However, you can typically arrow to them if they're part of grouped navigation widget.
Use setTimeout
with element.focus()
to set focus
Do not use createEvent()
, initEvent()
and dispatchEvent()
to send focus to an element, because DOM focus events are considered informational only -- generated by the system after something is focused, but not actually used to set focus. The timeout is necessary in both IE and Firefox 1.5, to prevent scripts from doing strange unexpected things as the user clicks on buttons and other controls. The actual code to focus an element will look something like this:
setTimeout("gFocusItem.focus();",0); // gFocusItem must be a global
Don't use :focus
or attribute selectors to style the focus
You will not be able to use :focus
or attribute selectors to style the focus if you want the focus to appear in IE. Set the style in an onfocus
event handler. For example, in a this.style.backgroundColor = "gray";
.
Always draw the focus for tabIndex="-1"
items and elements that receive focus programatically
IE will not automatically draw the focus outline for items that programatically receive focus. Choose between changing the background color via something like this.style.backgroundColor = "gray";
or add a dotted border via this.style.border = "1px dotted invert"
. In the dotted border case you will need to make sure those elements have an invisible 1px border to start with, so that the element doesn't grow when the border style is applied (borders take up space, and IE doesn't implement CSS outlines).
Use onkeydown
to trap key events, not onkeypress
IE will not fire keypress
events for non-alphanumeric keys.
Prevent used key events from performing browser functions
If a key such as an arrow key is used, prevent the browser from using the key to do something (such as scrolling) by using code like the following:
<span tabIndex="-1" onkeydown="return handleKeyDown();">
If handleKeyDown()
returns false
, the event will be consumed, preventing the browser from performing any action based on the keysroke.
Use key event handlers to enable activation of the element
For every mouse event handler, a keyboard event handler is required. For example, if you have an onclick="doSomething()"
you may also need onkeydown="return event.keyCode != 13 || doSomething();"
in order to allow the Enter key to activate that element.
Use try/catch to avoid JavaScript errors
This system is not currently supported by Opera, Safari, and older versions of Mozilla (1.7 and earlier). Because some browsers don't support the new capabilities like the tabIndex
property on all elements, use try/catch where appropriate. The widgets should still be usable with the mouse on those browsers that don't support the DHTML key navigation system. Support is planned for Opera and Safari (via WHATWG specs).
Don't rely on consistent behavior for key repeat, at this point
Unfortunatelyonkeydown
may or may not repeat depending on what OS you're running on. See {{template.Bug(91592)}} in the Bugzilla database.