JavaScript Keyboard Accessibility

The following is republished from the Tech Times #161.

JavaScript accessibility is an issue fraught with controversy and imperfect solutions, particularly when it comes to supporting the screen reader software that many visually impaired users rely on. These difficulties have led many developers to give up on accessibility entirely, when making your JavaScript accessible to some users can be refreshingly straightforward!

One group of users that it’s often very practical to accommodate in your JavaScript-powered web applications is keyboard users. Often due to a lack of fine motor control, these users get by without a mouse and instead navigate around the Web using the keyboard.

Let’s look again at the accordion control that I introduced in Tech Times #159, and see how we can make it work for keyboard users.

Here’s what the structure of the accordion’s HTML looks like:

<ul class="accordion">
  <li>
    <h2>Jonathan Archer</h2>
    <p>Vessel registry: NX-01</p>
    <p>Assumed command: 2151</p>
    ...
  </li>
  ...
</ul>

The way this control works for mouse users is that the heading of each fold of the accordion is clickable:

var folds = accordion.getElementsByTagName("li");
for (var i = 0; i < folds.length; i++)
{
  var foldTitle = folds[i].getElementsByTagName("h2");
  addEvent(foldTitle, "click", Accordion.clickListener);
}

When the user clicks one of these headings, a function is called that expands the corresponding fold, and collapses all the others:

clickListener: function(event)
{
  var fold = this.parentNode;
  Accordion.expand(fold);
  preventDefault(event);
},

Now, let’s think about how this script will affect keyboard users. The script collapses the accordion when the page first loads, hiding its contents. As it stands, keyboard users have no way of accessing that collapsed content.

Mouse users can click on any element in the document, but keyboard users can only “click” on keyboard-focusable elements. Typically, a keyboard user will repeatedly hit the Tab key (or the A key in Opera) to move the keyboard focus to the desired element, then hit Enter to initiate a “click”. By default, however, headings like the <h2> tags in our accordion are not keyboard-focusable.

To overcome this issue, you need to do one of two things:

  • Make the non-keyboard-focusable element keyboard-focusable.
  • Add to the document an element that is keyboard-focusable, like a hyperlink.

The first option would be ideal—in a perfect world. In Firefox 1.5 or later and Internet Explorer 5 or later, you can set the tabIndex property of a non-keyboard-focusable HTML element to zero, and it will magically become keyboard focusable. Unfortunately, this trick is not specified in any standard, and isn’t supported by other browsers like Safari and Opera.

The alternative, thankfully, works well enough in most situations: just add a hyperlink to the document where you want keyboard users to be able to focus and click.

Elegant as this solution is, there is one issue to consider: what URL is the link going to link to? If you insert the link directly into your HTML code, it needs to link someplace that will make sense when JavaScript is disabled:

<ul class="accordion">
  <li id="archer">
    <h2><a href="#archer">Jonathan Archer</a></h2>
    <p>Vessel registry: NX-01</p>
    <p>Assumed command: 2151</p>
    ...
  </li>
  ...
</ul>

If you can’t figure out an appropriate target for the link, however, you can always insert the link into the page dynamically using JavaScript, so that it will not be present when JavaScript is disabled. If you do this, you can safely point the link just about anywhere ("#" and "javascript:;" are common choices).

You can now adjust your JavaScript code to listen for “clicks” (both the mouse and keyboard varieties) on the link instead of the heading:

var folds = accordion.getElementsByTagName("li");
for (var i = 0; i < folds.length; i++)
{
  var foldLinks = folds[i].getElementsByTagName("a");
  var foldTitleLink = foldLinks[0];
  addEvent(foldTitleLink, "click", Accordion.clickListener);
}

A minor change to the clickListener function will also be necessary to account for the added depth of the clicked element:

clickListener: function(event)
{
  var fold = this.parentNode.parentNode;
  Accordion.expand(fold);
  preventDefault(event);
},

In many real-world scripts, providing accessibility for keyboard users really can be that easy.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • http://ian.sundermedia.com IanMuir

    Accessibility issues like this seem to be on the back burner for a lot of developers working with JavaScript. It’s good to see articles like this showing that it’s really not hard to make DHTML more accessible.

    Thanks for the great post Kevin.

  • Sojan80

    Hey Kevin, great article… I’m glad to see someone focusing on this topic myself.

    Any news on when the new book is coming out?

  • http://www.sitepoint.com/ Kevin Yank

    Thanks, Sojan80. The book continues to schedule and looks set for a June release.

  • Grey

    If you use “addEvent” anyways, why don’t you rewrite the “addEvent” function to also mutate the element?

    Thus, every time you add a click-event, you mutate that element to become a link (or child of a link). No need for markup hacks and you don;t need to remember do to that every time…

  • http://www.sitepoint.com/ Kevin Yank

    @Grey,

    Yes, that approach is mentioned in the article above:

    If you can’t figure out an appropriate target for the link, however, you can always insert the link into the page dynamically using JavaScript, so that it will not be present when JavaScript is disabled. If you do this, you can safely point the link just about anywhere ("#" and "javascript:;" are common choices).

  • David Hucklesby

    <a href=”#”> may be problematical, as some browsers take this as a link to the top of the page.

    I suggest <a href=”#void”> – with no real fragment ID of “void”.

  • Grey

    @Kevin: OK, thanks for pointers. I was confused by your use of “#” or “javascript:;”. If you add the link, you don’t need to have a href attribute, as far as I know…