Key-navigable custom DHTML widgets

  • Revision slug: Key-navigable_custom_DHTML_widgets
  • Revision title: Key-navigable custom DHTML widgets
  • Revision id: 143558
  • Created:
  • Creator: Mgjbot
  • Is current revision? No
  • Comment robot Adding: [[fr:Contrôles DHTML personnalisés navigables au clavier]]

Revision Content

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
menu item's focus handler add 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

Unfortunately onkeydown may or may not repeat depending on what OS you're running on. See {{template.Bug(91592)}} in the Bugzilla database.
{{ wiki.languages( { "fr": "fr/Contr\u00f4les_DHTML_personnalis\u00e9s_navigables_au_clavier", "ja": "ja/Key-navigable_custom_DHTML_widgets" } ) }}

Revision Source

<p>
</p>
<h3 name="The_problem:_today.27s_DHTML_pages_lack_keyboard_accessibility"> The problem: today's DHTML pages lack keyboard accessibility </h3>
<p>An increasing number of web applications are using <a href="en/JavaScript">JavaScript</a> 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 <code><span class="plain">&lt;div&gt;</span></code> and <code><span class="plain">&lt;span&gt;</span></code> 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.
</p><p>Here's a real example: most <a href="en/DHTML">DHTML</a> 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 <code><span class="plain">&lt;a&gt;</span></code> 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.
</p><p>It's now possible for HTML authors to do the right thing.  Making these widgets compatible with assistive technologies is documented under <a href="en/Accessible_DHTML">Accessible DHTML</a>.
</p>
<h3 name="The_solution:_changes_to_standard_behavior_of_tabindex"> The solution: changes to standard behavior of tabindex </h3>
<p>Firefox 1.5 follows Microsoft Internet Explorer's lead by extending the <code>tabindex</code> attribute to allow any element's focusability to be altered. By following the <a class="external" href="http://msdn.microsoft.com/workshop/author/dhtml/reference/properties/tabindex.asp">IE system for <code>tabindex</code></a> we're allowing <a href="en/DHTML">DHTML</a> 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.
</p><p>The following table describes the new <code>tabindex</code> behavior: 
</p>
<table class="fullwidth-table">
<tbody><tr>
<th><code>tabindex</code> attribute</th>
<th>Focusable with mouse or JavaScript via <code>element.focus()</code></th>
<th>Tab navigable</th>
</tr>
<tr>
<td>not present</td>
<td>Follows the default behavior of element (yes for form controls, links, etc.).</td>
<td>Follows default behavior of element.</td>
</tr>
<tr>
<td>Negative (e.g. <code>tabindex="-1"</code>)</td>
<td>Yes</td>
<td>No, author must focus it with <code>element.focus()</code> as a result of arrow or other key presses.</td>
</tr>
<tr>
<td>Zero (e.g. <code>tabindex="0"</code>)</td>
<td>Yes</td>
<td>In tab order relative to element's position in document.</td>
</tr>
<tr>
<td>Positive (e.g. <code>tabindex="33"</code>)</td>
<td>Yes</td>
<td>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 <code>tabindex="0"</code> or that are naturally tabbable.</td>
</tr>
</tbody></table>
<h3 name="How_to_use_the_new_system"> How to use the new system </h3>
<p>To make simple tab navigable widgets, the solution is to use <code>tabindex="0"</code> on the <code><span class="plain">&lt;div&gt;</span></code> or <code><span class="plain">&lt;span&gt;</span></code> representing it. Here's an example of a <a class="external" href="http://www.mozilla.org/access/dhtml/class/checkbox">span-based checkbox</a> that is keyboard accessible in both Firefox 1.5 and IE (although the <code>:before</code> rule for the checkbox image doesn't work in IE).
</p><p>For grouping widgets -- such as menus, tab panels, grids, or tree views -- the parent element should have <code>tabindex="0"</code>, and each decendent choice/tab/cell/row should have <code>tabindex="-1"</code>. A keydown event that watches for arrow keys can then use <code>element.focus()</code> to set the focus on the appropriate decendent widget and style it so that it appears focused. Here's an example of a <a class="external" href="http://www.mozilla.org/access/dhtml/class/tree">DHTML tree view</a> that is keyboard and screen reader accessible in Firefox nightlies (we're still finishing the work to make it work in IE).
</p><p>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.
</p>
<h3 name="Authoring_tips"> Authoring tips </h3>
<h4 name="Use_onfocus_to_track_the_current_focus"> Use <code>onfocus</code> to track the current focus </h4>
<p>The events <code>onfocus</code> and <code>onblur</code> can now be used with every element. There is no standard <a href="en/DOM">DOM</a> interface to get the current document focus, so if you want to track that you'll have to keep track of it in a <a href="en/JavaScript">JavaScript</a> variable.
</p><p>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.
</p>
<h4 name="Dynamically_change_focusability_using_the_tabIndex_property"> Dynamically change focusability using the <code>tabIndex</code> property </h4>
<p>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.
</p>
<h4 name="Use_setTimeout_with_element.focus.28.29_to_set_focus"> Use <code>setTimeout</code> with <code>element.focus()</code> to set focus </h4>
<p>Do not use <code>createEvent()</code>, <code>initEvent()</code> and <code>dispatchEvent()</code> 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:
</p>
<pre class="eval">setTimeout("gFocusItem.focus();",0);  // gFocusItem must be a global
</pre>
<h4 name="Don.27t_use_:focus_or_attribute_selectors_to_style_the_focus"> Don't use <code><span class="plain">:focus</span></code> or attribute selectors to style the focus </h4>
You will not be able to use <code>:focus</code> or attribute selectors to style the focus if you want the focus to appear in IE. Set the style in an <code>onfocus</code> event handler. For example, in a <div> menu item's focus handler add <code><span class="plain">this.style.backgroundColor = "gray";</span></code>.
<h4 name="Always_draw_the_focus_for_tabindex.3D.22-1.22_items_and_elements_that_receive_focus_programatically"> Always draw the focus for <code>tabindex="-1"</code> items and elements that receive focus programatically </h4>
<p>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).
</p>
<h4 name="Use_onkeydown_to_trap_key_events.2C_not_onkeypress"> Use <code>onkeydown</code> to trap key events, not <code>onkeypress</code> </h4>
<p>IE will not fire <code>keypress</code> events for non-alphanumeric keys.
</p>
<h4 name="Prevent_used_key_events_from_performing_browser_functions"> Prevent used key events from performing browser functions </h4>
<p>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:
</p>
<pre class="eval"><span class="plain">&lt;span tabindex="-1" onkeydown="return handleKeyDown();"&gt;</span> 
</pre>
<p>If <code>handleKeyDown()</code> returns <code>false</code>, the event will be consumed, preventing the browser from performing any action based on the keysroke.
</p>
<h4 name="Use_key_event_handlers_to_enable_activation_of_the_element"> Use key event handlers to enable activation of the element </h4>
<p>For every mouse event handler, a keyboard event handler is required. For example, if you have an <code>onclick="doSomething()"</code> you may also need <code>onkeydown="return event.keyCode != 13 || doSomething();"</code> in order to allow the Enter key to activate that element.
</p>
<h4 name="Use_try.2Fcatch_to_avoid_JavaScript_errors"> Use try/catch to avoid JavaScript errors </h4>
<p>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 <code>tabIndex</code> 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 <a class="external" href="http://whatwg.org/">WHATWG</a> specs).
</p>
<h4 name="Don.27t_rely_on_consistent_behavior_for_key_repeat.2C_at_this_point"> Don't rely on consistent behavior for key repeat, at this point </h4>
Unfortunately <code>onkeydown</code> may or may not repeat depending on what OS you're running on. See {{template.Bug(91592)}} in the Bugzilla database.</div>
{{ wiki.languages( { "fr": "fr/Contr\u00f4les_DHTML_personnalis\u00e9s_navigables_au_clavier", "ja": "ja/Key-navigable_custom_DHTML_widgets" } ) }}
Revert to this revision