Originally introduced as part of HTML 4, the
tabindex global attribute gives authors the means to define the order in which elements will receive focus when navigated by the user via the keyboard. The detailed behavior has now changed and is covered in the HTML 5 draft specs. All major browsers now implement the revised design.
The following table describes
tabindex behavior in modern browsers:
|not present||Follows the platform convention of the element (yes for form controls, links, etc.).||Follows the platform convention of the element.|
||Yes||No; author must focus the element with
||Yes||In tab order relative to element's position in document.|
Example 1: A simple image-based checkbox widget using tabindex to allow keyboard access
<!-- Without the tabindex attributes, the <span> elements would not be keyboard focusable --> <div> <span role="checkbox" aria-checked="true" tabindex="0"> <img src="checked.gif" role="presentation" alt="" /> Include decorative fruit basket </span> </div> <div> <span role="checkbox" aria-checked="true" tabindex="0"> <img src="checked.gif" role="presentation" alt="" /> Include singing telegram </span> </div> <div> <span role="checkbox" aria-checked="false" tabindex="0"> <img src="unchecked.gif" role="presentation" alt="" /> Require payment before delivery </span> </div>
For grouping widgets such as menus, tab panels, grids, or tree views, the parent element should be in the tab order (
tabindex="0"), and each descendent choice/tab/cell/row should be removed from the tab order (
tabindex="-1"). Users should be able to navigate the descendent elements using arrow keys. (For a full description of the keyboard support that is normally expected for typical widgets, see the DHTML Style Guide.)
The example below shows this technique used with a nested menu control. Once keyboard focus lands on the containing
Example 2: A menu control using tabindex to control keyboard access
<ul id="mb1" tabindex="0"> <li id="mb1_menu1" tabindex="-1"> Font <ul id="fontMenu" title="Font" tabindex="-1"> <li id="sans-serif" tabindex="-1">Sans-serif</li> <li id="serif" tabindex="-1">Serif</li> <li id="monospace" tabindex="-1">Monospace</li> <li id="fantasy" tabindex="-1">Fantasy</li> </ul> </li> <li id="mb1_menu2" tabindex="-1"> Style <ul id="styleMenu" title="Style" tabindex="-1"> <li id="italic" tabindex="-1">Italics</li> <li id="bold" tabindex="-1">Bold</li> <li id="underline" tabindex="-1">Underlined</li> </ul> </li> <li id="mb1_menu3" tabindex="-1"> Justification <ul id="justificationMenu" title="Justication" tabindex="-1"> <li id="left" tabindex="-1">Left</li> <li id="center" tabindex="-1">Centered</li> <li id="right" tabindex="-1">Right</li> <li id="justify" tabindex="-1">Justify</li> </ul> </li> </ul>
When a custom control becomes disabled, remove it from the tab order by setting
tabindex="-1". Note that disabled items within a grouped widget (such as menu items in a menu) should remain navigable using arrow keys.
Managing focus inside groups
When a user tabs away from a widget and returns, focus should return to the specific element that had focus, for example, the tree item or grid cell. There are two techniques for accomplishing this:
tabindex: programmatically moving focus
aria-activedescendant: managing a 'virtual' focus
Technique 1: Roving tabindex
tabindex of the focused element to "0" ensures that if the user tabs away from the widget and then returns, the selected item within the group retains focus. Note that updating the
tabindex to "0" requires also updating the previously selected item to
tabindex="-1". This technique involves programmatically moving focus in response to key events and updating the
tabindex to reflect the currently focused item. To do this:
Bind a key down handler to each element in the group, and when an arrow key is used to move to another element:
- programmatically apply focus to the new element,
- update the
tabindexof the focused element to "0", and
- update the
tabindexof the previously focused element to "-1".
Here's an example of a WAI-ARIA tree view using this technique.
Use element.focus() to set focus
Do not use
dispatchEvent() to send focus to an element. DOM focus events are considered informational only: generated by the system after something is focused, but not actually used to set focus. Use
Use onfocus to track the current focus
Don't assume that all focus changes will come via key and mouse events: assistive technologies such as screen readers can set the focus to any focusable element. Track focus using
Technique 2: aria-activedescendant
This technique involves binding a single event handler to the container widget and using the
aria-activedescendent to track a "virtual" focus. (For more information about ARIA, see this overview of accessible web applications and widgets.)
aria-activedescendant property identifies the ID of the descendent element that currently has the virtual focus. The event handler on the container must respond to key and mouse events by updating the value of
aria-activedescendant and ensuring that the current item is styled appropriately (for example, with a border or background color). See the source code of this ARIA radiogroup example for a direct illustration of how this works.
Note that the use of this pattern requires the author to ensure that the current focused widget is scrolled into view. You should be able to use the
element.scrollIntoView() function, but we recommend confirming this works for you in your target browsers using the quirksmode test.
Use onkeydown to trap key events, not onkeypress
IE will not fire
keypress events for non-alphanumeric keys. Use
Ensure that keyboard and mouse produce the same experience
To ensure that the user experience is consistent regardless of input device, keyboard and mouse event handlers should share code where appropriate. For example, the code that updates the
tabindex or the styling when users navigate using the arrow keys should also be used by mouse click handlers to produce the same changes.
Ensure that the keyboard can be used to activate element
To ensure that the keyboard can be used to activate elements, any handlers bound to mouse events should also be bound to keyboard events. For example, to ensure that the Enter key will activate an element, if you have an
onclick="doSomething()", you should bind
doSomething() to the key down event as well:
onkeydown="return event.keyCode != 13 || doSomething();".
Don't use :focus to style the focus (if you care about IE 7 and earlier)
IE 7 and earlier versions do not support the
:focus pseudo-selector; do not use it to style focus. Instead, set the style in an
onfocus event handler, for example by adding a CSS style name to the
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).
Prevent used key events from performing browser functions
If your widget handles a key event, prevent the browser from also handling it (for example, scrolling in response to the arrow keys) by using your event handler's return code. If your event handler returns
false, the event will not be propagated beyond your handler.
<span tabindex="-1" onkeydown="return handleKeyDown();">
false, the event will be consumed, preventing the browser from performing any action based on the keystroke.
Don't rely on consistent behavior for key repeat, at this point
onkeydown may or may not repeat depending on what browser and OS you're running on.