Managing :focus styles without breaking accessibility


I created a newer version of trackFocus that includes more generic event detection (including touch and pointer), only adds a single attribute to the body instead of littering the DOM with classes and exposes a small API for scripting. Check out What Input?.

We’ve all done it at one time or another. The browser’s default focus style for links and form controls doesn’t mix well with a design we’re building, so either at someone’s request or of our own volition, we throw something like this into our CSS:

 *, *:focus { outline: none; }

It’s seems harmless. I mean, how many problems could such a small declaration cause?

Why focus styles are significant

For anyone using the keyboard to navigate a page, focus styles are critical for visually displaying which focusable element is currently selected. Mouse users don’t have to worry about this because their eyes are already tracking the cursor as it moves around the page. Using the tab key to navigate a page, however, is like playing focus bingo -- you don’t know where focus is going to wind up next. Without a marker (let’s keep the metaphor going), you’re definitely going to lose.

This affects a wide range of users, including those who have:

  • Motor disabilities and have to use the keyboard.
  • Visual impairments and need a strong visual indication of where they are interacting with the page.
  • Cognitive disabilities (memory or attention) and may be prone to losing track of which task they are trying to complete.

The W3C had a conversation about this and agreed that:

The use of outline:none has a major effect upon all keyboard only users regardless of visual impairment.

Getting tripped up in our day-to-day work

Ideals are great until we’re elbow-deep in code. On a recent project I had coded a navigation element as a <button> because it was there to trigger a panel to open via JavaScript and not actually link to anything. The button was then styled as a link, and everything looked and worked great except for one thing: in some browsers, clicking the button gives it visual focus, which is the default behavior. This wasn’t just odd to look at -- it visually pulled my attention away from the panel that I had just opened.</p> <p><img alt="Button element receiving focus on click" src="" style="width: 837px; height: 121px;" /></p> <p>On a different project we might need to tightly control focus as it moves around an app, or simply maintain visual consistency with a design. Either way, something that we generally want to leave alone can wind up needing attention.</p> <p>Wanting to hide the mechanics of how that navigation was constructed without losing accessibility, I looked into techniques for determining when a user interacts with the page using a mouse versus a keyboard.</p> <h2>Enter jquery.keyboard-focus.js</h2> <p>There are several approaches to this problem, but I found a perfectly simple solution by Andrew Ramsden over on his site at <a href=""></a>. Andrew’s script uses jQuery to watch the body for the mousedown and <code>keydown events and saves which event happened and when it happened as data attributes. The script then watches for the focusin and <code>focusout events and uses the last device used to add a style to the element receiving focus. Then it’s just a matter of adding some CSS to style or un-style that element.</p> <h2>My own take: using modern web APIs</h2> <p>I loved this technique but wanted to update it to remove the dependence on jQuery and use current web APIs. The result is <a href="">trackFocus.js</a>. Just add the <a href="">minified version</a> (just 500 bytes) to your project and it automatically adds the focus--keyboard or <code>focus--mouse class to an element when it receives focus. You can then use those classes to style or un-style as needed! Check out the demo page to see it in action.

The code works as-is in all modern browsers. If you need to support older browsers, like IE8, you’ll need to grab these polyfills (or whatever version you might prefer):

Think I missed something? Let me know. Think you can make it better? Please fork the code and contribute.

Further reading

Jeremy leads Viget's front-end development team, and has helped make accessibility part of every site we build. Based in our Boulder, CO, office, Jeremy has worked with clients like Time Life, PUMA, and Dick's Sporting Goods.

More posts by Jeremy