In Ensure Web Pages are Accessible, I discuss how every page I publish is built ground up with accessibility elements in mind. The WAI/WCAG requirement for the ability to skip navigation links is often overlooked in designing web pages.
This post covers how I implemented skip navigation links on this site and (mostly) follows the recommendations of WebAim.
First watch it in action. The full circle navigation shows best on the home page or any page with series links at the bottom such as the HTML/CSS series. Set aside your mouse and navigate a page using only the Tab, Enter, Shift, and other keys on your keyboard.
You will notice as you approach navigation links, a "skip navigation" link comes up. Press Enter when it does, it will bring you to the main content block. Tab through to the series link, that skip link will skip to the sidebar, that skip link will bring you to the footer links, that skip link will bring you back to the top. If you’re unfamiliar with keyboard navigation and hit Tab instead of Enter on the skip nav link, hold down the Shift key to navigate in reverse.*
In it’s most simple form a skip navigation link only needs the ID of the page element you want to skip to in the href attribute of the anchor.
<a href="#content" class="skip-navigation">Skip Navigation</a>
This works well enough by itself, but there are two issues with this approach. The first is the page actually navigates to the targeted element you’ve specified, scrolling to it on the page. This means if you want to get back to the header, you now have to scroll back up or hit your back button. It also has the side effect that the URL in the browser location field now has the anchor link, and you have to hit your back button to remove it and get back to the unscrolled position.
The second problem with a simple link is if you don’t want the link showing in the design, you have to somehow hide it. When tabbing around a page, browsers will not focus on an element with display:none or visibility:hidden. In order for the skip navigation links to receive focus (so you can show them,) I use the method described on WebAim and absolutely position the link off the page. In order for this to work and come back to the place you want it (over the natural navigation in some way,) you must set the parent container to position:relative as well. (There’s a couple ways to do this, I’ve used them both.)
.menu, #sidebar ul {
position: relative;
}
li.skip-navigation {
position: absolute;
left: -3000px;
}
For the "visible" state of the links, "it depends." You will see I have skip nav links on the header, footer, sidebar, and any of the article series links (home page shows this pretty well.) For the header and footer, I wanted the links to "hover" over the existing links so it doesn’t push stuff around. For the sidebar and inline series links, I wanted the skip navigation link to insert itself at the start of the links and go ahead and make room for itself. The hovering links I left position:absolute and the inline links are set to position:relative. I only need to set left and top for all.
#sidebar li.skip-navigation.visible,
.highlight-nav-list li.skip-navigation.visible {
position: relative;
left: 0;
top: 0;
}
#menu li.skip-navigation.visible {
left: -6em;
top: -1em;
}
#menu-bottom-menu li.skip-navigation.visible {
left: 1em;
top: -2.5em;
}
Now we can leverage the jQuery focus() method to focus on the target element without changing the URL, which brings us to another problem: while focus() inherently works on form elements, it does not work on other DOM elements unless they have a tabindex attribute. WordPress makes this a little more difficult in that the default menu settings and the dynamic_sidebar() allow you to set classes, but they do not allow you to add a new attribute.
This could be solved by extending the Walker class to get complete control over the menu source or writing yet another hook but in this case I took a more simple approach. Since the show/hide of the skip navigation links are driven by Javacript/jQuery, Javascript is a legitimate solve for this problem.** On load of the document I used Javascript/jQuery to add the tabindex attributes to those elements. The elements in templates are straightforward, just add tabindex to the #header and #content elements.
$(document).ready(function() { addTabIndexToDom() // More code here for attaching event handlers }); /** * Wordpress doesn't let you muddle with the menus much unless you want to * extend the Walker. All we need to do is add tab indexes to a couple * elements so .focus() works properly. As a workaround and a lot less * code, on load add the tabindexes to the two places we need them. * * @return void */ function addTabIndexToDom() { var elements = $('#sidebar, #menu-bottom-menu'); var index = -2; $.each(elements, function() { $(this).attr('tabindex', index); index--; }); }
Note that this starts at -2 and decrements the tabindex for each element. In my templates I already have a -1 index. Using a negative number ensures the additions don’t break the normal document tab order.
There’s a little more going on in Javascript you can probably figure out. I’ve attached focus blur events to the skip nav links themselves to toggle their show/hide states in which it simply adds or removes class visible. I’ve also attached touchstart click events that trigger a check for e.type === 13 on keypress, with a fallback default action on touchstart. That way on a desktop the "skip link" is only triggered by the enter key.
* To-do for me, when the last skip nav is navigated the page should also scroll up, but if the user is actually visually impaired, the scroll position won’t matter.
** Normally I try to avoid mucking with the DOM by adding elements on which to rely unless I really have to. Exceptions are working with frameworks in which modifying the DOM is just how they work.