Docs/Keyboard navigable JS widgets

From CodeTalks

Contents

Key-navigable custom JavaScript widgets

Web applications often use JavaScript to mimic desktop widgets such as 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. This document describes how to make these widgets accessible with the keyboard.research papers

Here's a real example: many JavaScript tree views don't act like regular tree views with respect to keyboard access. A common mistake is to put each tree item in the tab order (often accomplished by making each menu item an <code><span class=plain><a></span></code> element). In fact, the tree should be in the tab order once, and arrow key navigation should be supported to move from tree item to tree item. This also true for other grouped navigation widgets such as menus, grids (spreadsheets), and tab panels.

For a list of typical widgets and the keyboard support that is normally expected for them, please see the DHTML Style Guide.

Solution: tabindex

Originally introduced as part of HTML4, the tabindex 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 HTML5 draft specs. All major browsers now implement the changes!

The following table describes <code>tabindex</code> behavior in modern browsers:

[| class=fullwidth-table ! <code>tabindex</code> attribute ! Focusable with mouse or JavaScript via <code>element.focus()</code> ! Tab navigable |- | not present | Follows the platform convention of the element (yes for form controls, links, etc.). | Follows the platform convention of the element. |- | Negative (e.g. <code>tabindex=-1</code>) | Yes | No; author must focus it with <code>focus()</code> as a result of arrow or other key presses. |- | Zero (e.g. <code>tabindex=0</code>) | Yes | In tab order relative to element's position in document. |- | Positive (e.g. <code>tabindex=33</code>) | Yes | Tabindex value determines where this element is positioned in the tab order: These elements will be positioned in the tab order before elements that have <code>tabindex=0</code> or that are naturally tabbable; For values greater than 0, smaller values will position elements earlier in the tab order than larger values (e.g. <code>tabindex=7</code> will be positioned before <code>tabindex=11</code>) |]

Simple controls

To make simple widgets tab navigable, use <code>tabindex=0</code> on the <code><span class=plain><div></span></code> or <code><span class=plain><span></span></code> representing it. Here's an example of a using this technique.

Grouping controls

For grouping widgets -- such as menus, tab panels, grids, or tree views -- the parent element should be in the tab order (<code>tabindex=0</code>), and each descendent choice/tab/cell/row should be removed from the tab order (<code>tabindex=-1</code>). An <code>onkeydown</code> event that watches for arrow keys can then use <code>element.focus()</code> to set the focus on the appropriate descendent widget and style it so that it appears focused. Here's an example of a using this technique.

Disabled controls

When a custom control becomes disabled, remove it from the tab order by setting <code>tabindex=-1</code>. 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

Technique 1: Roving tabindex

This technique involves programmatically moving focus in response to key events and updating the tabindex to reflect the currently focused item.

Bind a keydown 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, and
  • update the tabindex of the focused element to 0

Setting the 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 <code>tabindex=-1</code>.

Tips

Use <code>onkeydown</code> to trap key events, not <code>onkeypress</code>

IE will not fire <code>keypress</code> events for non-alphanumeric keys. Use <code>onkeydown</code> instead.

Use <code>element.focus()</code> to set focus

Do not use <code>createEvent()</code>, <code>initEvent()</code> and <code>dispatchEvent()</code> 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. One of my friends recommended me to order custom writing on EssaysProfessors.Com. One of my friends recommended me to order custom writingon EssaysProfessors.Com. To tell you the truth, I have never regretted my decision. The writers are real professionals and know how to write impressive work full of knowledgeable information. To tell you the truth, I have never regretted my decision. The writers are real professionals and know how to write impressive work full of knowledgeable information. Use <code>element.focus()</code> instead.

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.

Use <code>onfocus</code> 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 <code>onfocus</code> and <code>onblur</code> instead.

<code>onfocus</code> and <code>onblur</code> 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.

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 <code>onclick=doSomething()</code>, you should bind <code>doSomething()</code> to the key down event as well: <code>onkeydown=return event.keyCode != 13 || doSomething();</code>.


Don't use <code>:focus</code> to style the focus (if you care about IE 7 and earlier)

IE 7 and earlier versions do not support the <code>:focus</code> pseudo-selector; do not use it to style focus. Instead, set the style in an <code>onfocus</code> event handler, for example by adding a CSS style name to the <code>class</code> attribute.

Always draw the focus for <code>tabindex=-1</code> 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 <code>this.style.backgroundColor = gray;</code> or add a dotted border via <code>this.style.border = 1px dotted invert</code>. 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 <code>false</code>, the event will not be propagated beyond your handler.

For example:

<code><span tabindex=-1 onkeydown=return handleKeyDown();></code>

If <code>handleKeyDown()</code> returns <code>false</code>, 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

Unfortunately <code>onkeydown</code> may or may not repeat depending on what browser and OS you're running on.

Technique 2: aria-activedescendant

This technique involves binding a single event handler to the container widget and using the <code>aria-activedescendent</code> to track a virtual focus. (For more information about ARIA, see this overview of accessible web applications and widgets.)

The <code>aria-activedescendant</code> 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 <code>aria-activedescendant</code> and ensuring that the current item is styled appropriately (e.g. with a border or background color). See the source code of this for a direct illustration of how this works.


Tips

Ensure that keyboard and mouse produce the same experience

As with the roving tabindex technique, ensure that the user experience is consistent regardless of input device: keyboard and mouse event handlers should share code where appropriate.

Ensure that the keyboard can be used to activate element

As with the roving tabindex technique, ensure that the keyboard can be used to activate elements by binding the same mouse click handers to the keyboard Enter key handler.

scrollIntoView

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.

Issues