Drop-down menus can be a great way to save screen real estate, but the standard drop-down has one major problem: it’s not accessible to people who use the keyboard to navigate.
Here’s how to fix that with jQuery.
First, the basic menu HTML:
<nav id="nav-main">
<ul>
<li><a href="javascript:;">Rubber Boots</a></li>
<li><a href="javascript:;">Unicycle Parts</a></li>
<li><a href="javascript:;">Dog Costumes</a>
<ul>
<li><a href="javascript:;">Pirate</a></li>
<li><a href="javascript:;">Clown</a></li>
<li><a href="javascript:;">Shark</a></li>
</ul>
</li>
<li><a href="javascript:;">Vintage Tubas</a></li>
<li><a href="javascript:;">Magical Items</a>
<ul>
<li><a href="javascript:;">Cloaks</a></li>
<li><a href="javascript:;">Wands</a></li>
<li><a href="javascript:;">Rings</a></li>
<li><a href="javascript:;">Glowing Orbs</a></li>
</ul>
</li>
<li><a href="javascript:;">Ostrich Feathers</a></li>
</ul>
</nav><!-- /#nav-main -->
Next, some basic CSS:
/* NAVIGATION */ /* reset global list styles */ nav ul, nav li { position: static; left: 0; list-style: none; padding: 0; margin: 0; } /* style main navigation */ #nav-main { float: right; background-color: #EEE; } #nav-main li { float: left; position: relative; /* set positioning context for nested lists */ } #nav-main li li { width: 100%; border-top: 1px solid; } #nav-main li ul { position: absolute; top: 2.25em; /* nested list must overlap parent anchor */ margin-left: 1.5em; z-index: 1000; width: 8em; /* adjust as needed for width of text */ background-color: #EEE; border: 1px solid; border-top: none; } #nav-main a:link, #nav-main a:visited { display: block; text-decoration: none; color: #000; padding: .75em; } #nav-main a:hover, #nav-main a:focus { background-color: #000; color: #FFF; }
The nested lists are absolutely positioned, relative to their parent list items. Now we move the nested lists offscreen with a left offset.
- A negative left offset value will send the nested lists to the left.
- A positive left offset value would send the nested lists to the right. We don’t want to do this, as it creates a wide page with horizontal scrolling.
#nav-main li ul { blah blah blah left: -999em; /* negative value sets nested list off the left side of the screen */ }
We bring the nested lists back into place by changing their left offset to auto when the parent list item is hovered or has focus:
#nav-main li:hover ul, #nav-main li:focus ul { left: auto; }
The Problem
However, this presents an accessibility problem. The browser knows when a list items is hovered, but list items are not natively focusable.
As this example shows, tabbing through the links by keyboard does not offer a good user experience.
The Solution
A little jQuery provides an elegant solution by adding a class name to the nested list when the parent li’s anchor has focus.
Add the script to your HTML:
<!-- load jQuery --> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script> <!-- add class="focused" to submenu list when parent anchor has focus --> <script> // parent $(function() { $('li a').focus( function () { $(this).siblings('li ul').addClass('focused'); }).blur(function(){ $(this).siblings('li ul').removeClass('focused'); }); // submenu $('li a').focus( function () { $(this).parents('li ul').addClass('focused'); }).blur(function(){ $(this).parents('li ul').removeClass('focused'); }); }); </script>
… and then change your li:focus selector from this:
#nav-main li:hover ul,
#nav-main li:focus ul {
left: auto;
}
… to this:
#nav-main li:hover ul,
#nav-main li ul.focused {
left: auto;
}
Now we have keyboard accessible drop-down navigation. Tab through the links to see it work.