Is your rich JavaScript app keyboard-friendly?

initial version 19.10.2012
reworded & improved 10.12.2012

Keyboard support for onclick statements on HTML tags
Accessibility demo test case & compatibility table


Question

So, you have a lot of code like this: <span onclick="foo()"> content </span> (or you dynamically attach event listeners).

Will I be able to use your page if I don't have a mouse? Or if I prefer keyboard?

TL;DR

For maximum portability, please use the following instead: <a tabindex="0" href="javascript:void(0);" onclick="foo()"> content </a>

Ideally, you should use buttons, but I doubt you will.


Introduction

Is your interactive site friendly for keyboard users?

If you have lots of onclick events bound in many places in your code to execute some JavaScript, under certain circumstances keyboard users will be able to work with it just fine, but it's not always the case, and it depends heavily on the browser. This page is supposed to be a review of pros and cons of various attitudes.

Why keyboard support matters

Disclaimer

Usage

All of the HTML tags below in the leftmost column have:

Tab-navigate through the items. Click Enter when each of the items has focus, to check whether onclick will be fired when you activate the item from keyboard.

Example IE8 IE7/IE9/IE10 Firefox3.6...Fx18 Opera 11.6...12.0 Chrome18...25 Safari 5.1.7
<a tabindex="0" href="javascript:void(0);"> onbeforeunload issues [6] opens blank new page on mouse middle click [1] not focusable via TABbing
by default! [4]
<a tabindex="0" href="#"> resets the current URL hash [2];
opens a copy of the page on middle click [7]
<a>any of above WITHOUT tabindex not focusable via TAB [5]
<a>WITHOUT href. focus can be lost after [3] not fired not fired not fired not fired
<span> / <div> / <p> focus can be lost after [3] not fired not fired not fired not fired
[1] Neither e.preventDefault() nor return false (inside onclick, onmousedown, onmouseup) change anything;
the only way I've found to disable the native event of opening a link in the new tab is to have some event listener attached to onmousedown invoke alert() (but 1] this works only in Firefox, and 2] is far from being user-friendly, so it should not be used).

However, since the newly opened page is blank, it's not a really big deal.

[2] Resetting the hash has also a side-effect of scrolling to the top. To prevent this, one can use #nonExistingAnchor in a link instead of just #, and then no scrolling would take place. A sensible convention can be to use #void as this anchor (and of course do not create any object on the page with that id). Furthermore, in modern versions of Firefox, Chrome and Opera, you can do a following hack (taking advantage of History API) to restore the old hash:
<a href="#void" onclick="alert('hi');">click me</a>
<script>
window.onhashchange = function(e){
   var newUrl = e.newURL, newUrlLen = newUrl.length;
   if(newUrl.substring(newUrlLen-5,newUrlLen)=="#void"){
      history.replaceState(null,null,e.oldURL);
   }
}
</script>
[3] Clicking ENTER when focus is on any non-form and non-link HTML element (like div, span etc.) -- even if that element doesn't have any event listeners attached -- astonishingly moves the focus to the closest <button> in the markup (!). Not the case if clicking the element by mouse. Likely an IE8 bug.

[4] User can opt in via Preferences > Advanced > "Press Tab to highlight each item on a webpage". The default behavior is ridiculous, but power users who want keyboard accessibility certainly will have the option enabled.

[5] TAB-navigation in Opera is separated from navigation over the links.

TAB-navigation cycles only over the elements that have explicitly set tabindex e.g. to 0 (and by default, over the form elements). It doesn't cycle over the links that do not have tabindex.

To cycle over the links, there are separate keyboard shortcuts CTRL-UP and CTRL-DOWN. The good side is that the two cooperate together -- can be used alternately; i.e. using (SHIFT+)TAB or CTRL-UP/DOWN moves the focus to the closest element of the currently focused one. Still, if one wants to force all links to be accessible also by TAB, the tabindex="0" can be added to them to achieve this.

By the way, remember not to override this shortcut (CTRL-UP/DOWN) if you're going to enable rich keyboard shortcuts in your application, as you can do more harm than good to the users of Opera who are accustomed to this browser shortcuts.
[6] If:
  • you have defined window.onbeforeunload
  • and your event listener does NOT return false
Then: Asks "Are you sure you want to navigate away from this page?" -- both from keyboard/mouse click.

Since you're using onbeforeunload you probably need to do that, so take a look at your listeners and add return false at the end of the handlers.
[7] In Chrome, opens a new tab only when current URL's hash is different than link href's hash.

Corollaries

To provide keyboard-navigability of your control on the page: