Fixing “Skip to content” links

**Update (15-Jan-2013): ** After a few tweets about this and some re-testing, it appears the issue discussed in this post only affects Internet Explorer and Chrome. The post has been updated to reflect this.

If you’ve been doing web development for any amount of time, you have probably come across the recommendation to create a “skip to content” or “skip navigation” link for accessibility purposes1. The idea is to have the first link on the page linked to the main content so that those who can’t use a mouse can easily avoid tabbing through all of the site navigation just to get to the main content. The first tab stop on the page is a “skip to content” link that simply links to the container of the main content. Depending on the site, the link might always be visible or might only be visible when focus is set to it. Either way, the HTML looks something like this:

<a href="#content">Skip to Content</a>

<!-- other links -->

<div id="content">
    <!-- your content here -->
</div>

Basically, this takes advantage of hashes for page navigation. The link to #content automatically moves the user down to the element whose ID is “content”. This type of navigation is very popular on large content sites where pages have a table of contents.

The problem

Although the “skip to content” link approach works most of the time, Internet Explorer 9 and Chrome aren’t obeying the rules. While the visual focus of the browser shifts to the element being linked to, the input focus stays where it was. For example, if I press tab and then enter on a “skip to content” link, the browser will scroll down to that element so I can read the content. If I then press tab again, the input focus moves to the next focusable element after the “skip to content” link, not to the next link in the content area.

The difference between visual focus and input focus is subtle. Visual focus is most often affected by scrolling. When you navigate to an element on the page using a hash, the browser scrolls that element into view, effectively changing the visual focus so you can read. If you use the up and down arrows, they interact with the scrollbar naturally. However, if you use tab to change focus then you end up scrolling back to the previous spot on the page. That’s because the input focus didn’t change along with the visual focus.

This is definitely a but in WebKit2 and potentially one in Internet Explorer (perhaps one that’s been around for a while3). The WebKit one is a bit more innocuous whereas the Internet Explorer one appears to be related to styling. If the element being targeted by the in-page link has a width and height set via CSS, then the focus is set appropriately. Without both dimensions specified, the input focus doesn’t shift in Internet Explorer.

A solution

The best solution I came up with is to use a small piece of JavaScript to create a better behavior. The basic idea is to watch for changes in the URL hash using onhashchange and then set focus to the element that now has visual focus. Since that element is usually a <div> or some other container, these elements aren’t the focusable by default (links and form controls are). To make them focusable, you need only set tabIndex to -1 before calling focus(). Doing so means that the element won’t be in the regular tab order but you can still set focus to it using JavaScript. Here’s the code:

window.addEventListener("hashchange", function(event) {

    var element = document.getElementById(location.hash.substring(1));

    if (element) {

        if (!/^(?:a|select|input|button|textarea)$/i.test(element.tagName)) {
            element.tabIndex = -1;
        }

        element.focus();
    }

}, false); 

I’ve chosen to use the DOM Level 2 method for adding an event handler, but you can just as easily use a browser agnostic approach. The hashchange event is supported back to Internet Explorer 8, so you may want to mix in attachEvent() if you’re supporting that browser as well. Of course, you could also just use a JavaScript library that abstracts that difference away from you.

What I like about this solution is that it doesn’t interfere with what the browser is doing as it shifts visual focus. All it does is wait for that shift to happen and then set focus to the element that you’re already looking at. That allows you to start tapping into the content area immediately while preserving the behavior of the back button.

Conclusion

The “skip to content” links can be a great aid to accessibility. Once Internet Explorer and Chrome fix their buggy implementations, the fix mentioned in this post won’t be necessary. As someone who uses only a keyboard on the web, I would greatly appreciate it if more sites used skips links to help with navigation.

  1. Skip Navigation Links (WebAIM)
  2. Skip links and other in page links in WebKit browsers (456 Berea Street)
  3. Keyboard Navigation and Internet Explorer (Juicy Studio)

Comments

  1. ken

    Is it not necessary to check if the element is a link or form control? E.g. would this not possibly upset the layout of the tabIndex's already set? It seems like you could very well end up with all elements on your page having a tabIndex of -1... but I may be missing something.

  2. Nicholas C. Zakas

    @Ken - That's a good point. Most of the sites I've come across where this was a problem tended to use non-focusable elements as the target so it wouldn't matter. You're right that if it were focusable, this would be mess up the tab order. If you're not sure what you're setting focus to, it's probably a good idea to double-check the element type.

  3. Thierry Koblentz

    Hi Nicholas,
    This is very interesting as I don't recall things working that way in the past.
    Actually, the behavior you describe corresponds to the way IE6 deals with skip links (or in-page links).
    Check this old article from Gez Lemon, it points out the different behavior between "modern browsers" (at the time) and IE6:
    http://juicystudio.com/arti...

  4. Sarah Clatterbuck

    If there is an anchor wrapping the target with the name attribute, it will also resolve the issue. Old school, but it works cross browser without JS.

    <a href="#content">Skip to content</a>

    <div id="myDiv"&gt
    <a name="content"></a&gt
    </div&gt

    If the empty anchor is semantically offensive, you can place some meaningful text in it and place off screen.

  5. Nicholas C. Zakas

    @Sarah - Hmm, when I tried that, it didn't have any effect in either IE9 or Chrome. That's why I went to the JavaScript way of doing things. I just tried it again now and it doesn't seem to work.

  6. Srinivasu

    Thanks Nicholas. Means a lot if someone like you advocates the skip links. Appreciate it. On a side note, I have recently posted about "Skip links - what, why and how?". Available for read on http://sgaccessibility.com/...

  7. Stomme poes

    Sarah's solution has never worked around the webkit bug, but there was a browser (can't remember who anymore) who did work if the destination was something focusable (such as a hidden link), and it was for this reason many of us had ugly things like <h2><a href="#myself" id="myself"> </a></h2> (and the lack of a space or any content within the anchor was for some reason an issue for Opera back then) in the code.

    But webkit has always needed the Javascript solution.

  8. Már

    Terrill Thompson wrote about this a while back - and came up with a different (and IMO much less elegant solution):

    Back to Basics: Skip to Main Content Links

  9. Sarah Clatterbuck

    @Nicholas - Shoot! You're right. This does not solve the tab focus issue. It only solves the scroll focus issue for browsers that don't do that properly to non-focusable elements. I guess we need the JS to make the solution completely useful for keyboard navigators.

    -Sarah

  10. Vishal Gupta

    @Ken - I don't think there is a use case when a skip-2-content-link will need to skip to content with the tabindex set. Because, tabindex-ed elements will always take priority over the skip-to-content link.

  11. Dowon Kang

    Just noticed textarea is missing in your solution.

  12. Nicholas C. Zakas

    @Dowon - thanks for the heads up.

Understanding JavaScript Promises E-book Cover

Demystify JavaScript promises with the e-book that explains not just concepts, but also real-world uses of promises.

Download the Free E-book!

The community edition of Understanding JavaScript Promises is a free download that arrives in minutes.