This is perhaps a bit of wheel re-invention, but a lot of my code is built from scratch so I can customise it easily. The issue is this: how to make simple drop-down navigation menus properly keyboard accessible?
I’ve usually been using CSS for navigation drop-downs – just a little tweak to the
display value of the sub-menu when the parent link gets
The problem is that while a keyboard-only user can tab into the main list of links, and sub-menus will be revealed when they approach one, when they tab again, they’re lost. I think the first link in the sub-menu may get focus, but then the parent link loses it, and the sub-menu disappears!
So, the solution has to be JS-powered. Here’s the code:
Have a look at the HTML. The ARIA attributes are included as per Terrill Thompson’s very useful post on keyboard-accessible menus. I’ve not tested on a screen reader – that aspect may need refining. They’re worth noting, though, as I do use the
aria-expanded attribute in the CSS and JS, as a useful and semantic handle to grab hold of. The last bit of the CSS is most important – to make sure sub-menus are hidden when
aria-expanded is set to
false on its parent container.
The JS is where it all happens. A
focus event on a menu item with children reveals the sub-menu. But the corresponding
blur event sets a small delay before proceeding, and then only hides the sub-menu if a certain data attribute isn’t set on the
.sub-menu-wrapper element. That data attribute is handled in the next two event handlers, which make use of jQuery’s focusin, attached to
.sub-menu-wrapper. So in the split second when a keyboard user tabs from a parent to a child link, that child link gaining focus will trigger the event handler which sets the wrapper’s
data-has-focus attribute to
true. Then when the
setTimeout code kicks in, the check fails, and the sub-menu isn’t hidden. When the user tabs past the final child link, then the sub-menu is hidden.
I’ve found some issues with IE (surely not!), although at present I’m not sure if they’re specific to the project I’ve developed this code in. The demo on Codepen above seems to work – I’ll post updates if I find improvement for poor old IE.
There’s probably a lot of refinements to mull over. For example, should the up/down arrows be usable to go through sub-menus? But I think this code supplies the foundations for most scenarios.
One closely related accessibility issue worth mentioning: make sure you include a proper ‘skip to content’ link! Otherwise this handy code which sends the tab focus through all the menus could become a tiresome obstacle. WordPress users may have noticed the ‘skip to toolbar’ link which appears on the front-end when you’re logged in and start tabbing. That’s the basic principle. The link should be hidden by default (using an accessible technique), and revealed when it gains focus. Finally, you may find this fix for issues in Chrome and IE with ‘skip to content’ links useful.
UPDATE 9/5/15: I’ve refined the JS to work better, particularly in IE. The main changes are: to attach the top-level focus/blur events to the links, not the list item elements; to add checks to the
focusout event for the
.sub-menu-wrapper, to see if another element inside sub-menu, or the parent link, has simultaneously gained focus; made use of a
has-focus class on the top-level link, due to jQuery no longer supporting the