Learning to focus()

Heydon Pickering
Share

Thanks to good fortune, I have a functional pair of eyes. When it comes to traversing web pages, this is something of a boon since shifting my focus from one part of the page to another requires nothing more than semi-consciously contracting my extra-ocular muscles and rotating my eyeballs a fraction of their circumference.

Though my visual — and, therefore, cognitive — focus could now be said to have changed, the user interface itself does not know my attention has turned. At least, not until I begin to physically interact with the newly scrutinized region. To put it another way, rotating my eyeballs is an unrecorded event.

Herein lies a very common but frequently unaddressed accessibility issue: If I’m not using my eyes (and my mouse hand to point), how do I ‘focus’ on something in order to then interact with it? The answer, as I’m sure you know, is via programmatic focus. It’s not as simple as just incorporating ‘focusable’ elements into our designs, however. Sometimes it is not the user but the interface on the user’s behalf that must move focus between different areas. This requires deliberate focus management on the part of the developer.

For accessibility, managing focus is essential in many of even the simplest JavaScript-driven interactive widgets, but correct usage is almost impossible to test for with automated QA tools. That’s why I want to take you through a couple of simple examples today.

ScrollTop Animation

In this example, I invite you to imagine you have ‘enhanced’ a same-page navigation link so that, instead of jumping abruptly to the link’s target fragment (#section1, let’s say), you are ushered gently to this destination via a JavaScript scrollTop animation. The important thing to note regarding accessibility when using this technique is the necessity to override the default function of the <a> element.

event.preventDefault();

By doing this you are telling the browser to avoid, at all costs, doing anything standard, expected or interoperable. You are telling the link not to link. In fact, the only way to make certain the link actually takes the user correctly to the page fragment is to turn JavaScript off. Well, then.

By simply linking to a page fragment — as one would with JavaScript off — the browser essentially focuses this fragment. This is where screen reader output and keyboard interaction is now based. By animating the scrollTop, no such focusing action takes place, meaning screen reader output and keyboard interaction continues from the area of the page that is no longer in view. This is no good.

The Remedy

We need to focus the destination fragment via JavaScript. First we need to make the fragment programmatically focusable to begin with, which requires use of the tabindex attribute. A tabindex value of -1 is a special value that means scripts can focus the element, but not users. This is preferable to tabindex: 0 in this case, because there’s no reason why this non-interactive element should be focusable using the TAB key.

Note: Thanks to Patrick and others in the comments for pointing out the fact that this should use “-1” instead of “0” for the tabindex value, which we’ve now corrected.

<section id="section1" tabindex="-1">
    ...
</section>

Our second task is to include the JavaScript focus() method in the callback of the animation, ensuring the fragment is focused after the animation ends.

document.getElementById('section1').focus();

Finally, it’s a good idea to record our sub-page location in the URL, as would have happened by simply linking to the fragment (e.g. http://my-site/#section1). This way we are able to copy the address as a link to the specific section (i.e. we can “deep link”). Include the following line after focus() has taken place:

window.location.hash = 'section1';

Of course, you would replace ‘section1’ with a variable based on whatever the corresponding link’s href is, minus the #.

<a href="#whatever">scroll to section 'whatever'</a>

The Result

A little CodePen demo is embedded below. Try using it with just TAB and SHIFT+TAB as a habitual keyboard user would.

See the Pen idxjL by SitePoint (@SitePoint) on CodePen.

Take note that we are adding the tabindex attribute on the fly in our JavaScript. Now that we are focusing the sections (fragments) to which we are scrolling, our perceived location is more than merely visual. That is, should I press the TAB key after moving to a new section, I will focus the next focusable element within that section; the hyperlink reading ‘heydonworks.com’, in the following example.

<section id="section1" tabindex="-1">
   <p>Donec a congue leo? Fusce ac sodales magna. Aliquam nisl enim… tristique tempus placerat at, <a href="http://heydonworks.com">heydonworks.com</a> posuere in lectus. Curabitur consectetur faucibus nisl ac varius.</p>
</section>

Had we not focused section1, pressing the TAB key would have focused the next element following the instigating link, which would jump the viewport back to our navigation block. In other words, keyboard users would be back to square one.

<a href="#section1">Section 1</a>
<a href="#section2">Section 2</a> <!-- not where we want to be -->

Closing Dialogs (Modal Windows)

Here’s another little example. Let’s pretend a user has pressed a <button> and it’s opened a dialog or modal window. For argument’s sake, this dialog is asking the user to confirm or cancel the would-be action instigated by the button.

To make opening this button accessible, we should focus() the dialog in much the same way as we focused the page fragment in the last example. Using jQueryUI, the first of the dialog’s buttons (‘confirm’, in our case) would be focused. Other implementations focus the dialog container. Either way, users are programmatically sent to the right place.

$('dialog button:first-of-type').focus();

The question is, ‘what happens to focus when the dialog closes?’ If we do nothing, the newly hidden dialog would necessarily lose focus but nothing would take its place. In many user agents, this would mean the <body> is focused by default, making users wade through the document to find their place again.

It would be more logical to refocus the element that opened the dialog. This is easy to do by just saving the DOM node in memory or to write a marker on it for later use, like this:

$(this).attr('data-dialog-trigger', true);

At the end of the dialog’s close() method, we would then just focus the original element:

$('[data-dialog-trigger]').focus();

Most screen readers, upon the button being refocused, would announce the title of the page, then the focused button. That way, you know where you are. An example of a dialog using this method is available for testing should you wish.

Conclusion

It is a common mistake to conflate progressive enhancement with accessibility; to think that making sure something degrades to working with JavaScript turned off (as it would in this case) means it is ‘accessible’. Sure, it makes it accessible to those without JavaScript, but most keyboard and screen reader users interact with your applications with the help of JavaScript like anyone else. The trick is to use JavaScript in a way that respects the differing behaviors and inputs of all your users and any assistive technologies they might use.

One more thing: If you are ever charged with working on a one page application built with, say, Ember.js or AngularJS, grep the codebase for the JavaScript focus() method. Such applications completely rewrite navigation using ‘views’; dynamic rebuilds of the singular page. Without some careful focus management, rebuilding the DOM in such a way is liable to screw up accessibility pretty quickly. If your grep finds less than a couple of .focus instances, there may be a lot of work to do.