Article
Accessible JavaScript: Beyond the Mouse
Page: 1 2
Looking for Behavioral Pairing, not Event Pairing
The HTML Techniques for WCAG 1.0 suggest that a good approach to catering for device independence is to provide redundant input events -- two handlers for the same element that "pair" together. The examples it gives include pairing keydown with mousedown, or using keyup to go with mouseup.
However, this is the wrong way of looking at the issue of providing device independence, because keyboard and mouse events are conceptually different things, and in many cases, behave completely differently. We'll see this difference in a moment, in the first of our practical example.
I think it's more helpful to think in terms of behavioral pairing, rather than event pairing. If you have a piece of functionality that's driven by, say, a mousedown event, don't think, "How can I use a keydown event to make this work?" Simply think, "How can I make this work from the keyboard?"
Am I splitting hairs? I don't think so. When it's thought of in this way, the question leads to different answers. The first question asks about a specific approach, which may or may not turn out to work; the second question simply asks if there is an approach; it's open to any compatible solution. In the last of our practical examples -- Drag 'n' Drop -- we'll see just how dramatic that difference in thinking can be.
Some Practical Examples
Let's look at some practical examples. I'm not going to delve too deeply into code here. This is just a basic review of some different types of scripting as they're implemented for the mouse; we'll also give some thought to how we might implement them for the keyboard.
Simple Rollovers and Revealing Content
A simple rollover effect might consist of a color or background-image change on a link. You're probably more than familiar with links that have block display applied, along with :hover and :focus pseudo-classes, so that they can have background swaps without the need for JavaScript.
Scripted rollovers are generally just as easily extended to the keyboard, providing that they use proper links or other focusable elements (not just plain text content elements, like a <span> or <td>). In our first example, we'll add a simple effect to a single element, triggered by toggling a class name (using a hypothetical addEvent function, for example; substitute this when you apply the code in your own work -- you can choose your favorite):
addEvent(link, 'mouseover', function()
{
link.className = 'rollover';
});
addEvent(link, 'mouseout', function()
{
link.className = '';
});
We can simply add a pair of focus and blur handlers to do the same job for people navigating via keyboard:
addEvent(link, 'focus', function()
{
link.className = 'rollover';
});
addEvent(link, 'blur', function()
{
link.className = '';
});
When it comes to handling events on groups of elements, the situation is more complicated, due to the fact focus events don't bubble. An event bubble occurs when an element passes the event it triggers up to its parent element. While we could handle a mouse event on any element using a single document-level listener (a technique that's sometimes known as event delegation), we can't do the same for events that don't bubble:
addEvent(document, 'mouseover', function(e)
{
var target = typeof e.target != 'undefined'
? e.target : e.srcElement;
//"target" is whatever node the event bubbles up from
});
This approach works because mouse events bubble up from the point at which they happen; however, since focus events don't bubble, such a function would only handle events that occur on the document node.
If we wanted to capture events on each of a group of elements, we'd have to iterate through the elements and bind a listener to each one individually:
var links = list.getElementsByTagName('a');
for(var i=0; i<links.length; i++)
{
addEvent(links[i], 'focus', function()
{
//and so on ...
});
}
Bear in mind that the exact translation of mouse to keyboard behaviors is not necessarily appropriate, because the usability concerns are often very different between these two kinds of behaviors. Consider the open and close timers in a DHTML menu; these are necessary for the mouse, but undesirable for the keyboard. After all, it's not possible for users to "slip off the edge" of the menu when navigating with their keyboards, so all the timers offer is useless pauses to the menu's actions.
AJAX and Other RPC Scripting
The core of AJAX scripting deals with programmatic events, such as the onreadystatechange event of an XMLHttpRequest object, or the load event of an iframe that's being used for data retrieval. The user's mode of interaction doesn't affect the behavior of these events, so we don't need to consider each mode of interaction specially.
However, we do have two important points to consider.
Firstly, and most obviously, we should consider how those processes are triggered in the first place. If a request or process is to be initiated by a user action, we must ensure that the action can be triggered by keyboard users. The solution is simply a matter of choosing an appropriate trigger element, as we've already discussed.
The second issue requires the careful construction of response HTML, to ensure that we maintain a useable tab order. If we create new content in response to a user action, and that new content is itself interactive, we must ensure that it's inserted at a logical point in the HTML.
For example, say we have a User Preferences form in which users specify their personal details. In this case, they must provide country of origin information:
<label for="country" id="country-selector">
<span>Country: </span>
<select id="country">
<option value=""></option>
<option value="uk">UK</option>
<option value="au">Australia</option>
</select>
</label>
<input type="button" value="Save details" id="save-button" />
We could attach to the select element an onchange event listener that runs code to create a secondary select that allows users to choose a county or state as appropriate. However, we'd want that secondary select to be accessible to the keyboard user immediately, so we should insert it in the correct place -- after the first label, before the button:
var button = document.getElementById('save-button');
button.parentNode.insertBefore(newselect, button);
This example assumes that the new selector and label has already been created, and saved to the object reference newselect.
Drag 'n' Drop
Drag 'n' Drop functionality requires complicated scripting at the best of times, whether or not you're trying to make it accessible! At first glance, the task of making this functionality accessible looks impossible, because the dynamo of drag 'n' drop is the mousemove event, for which there is no keyboard equivalent. But with a bit of lateral thinking, it can be done!
Imagine our application contains a vertical list or column of boxes that users can drag 'n' drop to re-order. The user's mouse picks up an object, moves it, then snaps it to a new position; the end result of the actions is simply a change in the order of the objects -- the one the user dragged has moved up or down by x number of spaces. Couldn't we achieve that same outcome using commands generated by the up and down arrow keys?
Indeed we could, but to do so, we'd need a trigger element for the keyboard: a focusable element (either the dragable object itself, or something inside it) that can handle events from the arrow keys.
In the image below, you can see a box that indicates mouse behaviors. The darker strip at the top is the trigger element for the mouse. Users click on this area and move their mice in order to drag the box around; hence, the principle active event for this behavior is mousemove:

Now if we add a link or button inside the dragable element, and style it to look like a graphical icon, that icon can be used the trigger element for the keyboard. Given this line of reasoning, the principle active event for the behavior is keypress:

From this example, we can see the futility of event pairing. There is very little functional similarity between mousemove and keypress events, yet those were the two events we needed to provide for mouse and keyboard users. The conceptual journey we stepped through in order to make this functionality work for the keyboard showed how we can achieve the ultimate goal -- equivalent functionality. The details of implementation are just that -- details.
These pictures are taken from an actual script, which is too large to reproduce here, but if you'd like to download and play with it you can find it on my web site.
Accessibility is not a Feature
In my imagination, there is no complication.
-- Kylie Minogue
Designing for accessibility is like building the foundations of a house -- easy if you do it from the start, but very difficult to hack in afterwards.
Clearly the best approach is to consider accessibility right from the project's initiation -- to recognize that accessibility is a design consideration, not a feature. Indeed, Joe Clark's evaluation of Basecamp's accessibility makes the point that if you look at accessibility as a feature, you'll probably just leave it out. "Most developers are gonna leave it out anyway; most developers don't know the first thing about accessibility or even that it's important." That's skeptical, sure, but nonetheless, it's true.
With that quote in mind, I'd like to finish by giving you an example of something cool and inspirational, something that really does exemplify best practice in this area. It isn't new (it's more than a year old, having been developed and presented by Derek Featherstone at Web Essentials 2005), but its sheer grace and simplicity still bowl me over: it's the Semantic, Accessible Crossword Puzzle.
We can't all be as talented as Derek! But on a practical, everyday level, I hope I've begun to demonstrate that device-independent scripting really isn't that difficult or complex. It may be different from the way we're used to working, but all it really takes is a little extra thought.