Responsive CSS3 Multi-Level, Drop-Down Menu (with JavaScript enhancement + keyboard 'tab-to' support)

This responsive menu is a step on from the CSS3 Multi-Level, Off-Canvas Mobile Menu (with JavaScript enhancement) and provides both a mobile and desktop view of the menu.

It is also a progression of its CSS-only, responsive counterpart and uses a bit of JavaScript to make sub-menus accessible via the TAB key on a keyboard in desktop view, and close all the menus/sub-menus in one go in mobile view. This is a bonus over the CSS-only version where sub-menus cannot be tabbed-to, and you have to close all opened sub-menus in turn. Please read the Notes section for details about JavaScript enhancement.

Desktop (> 960px): You'll see a familiar-looking horizontal, drop-down menu bar at the top of the screen...

Mobile (< 960px): Give that    icon in the top-right corner a click to open the "off-canvas" menu...


A CSS3, responsive menu that;

* Although an infinite number of nested sub-menus is possible, you'll probably want to stop at 2 or 3 deep due to sub-menus progressively flying right in desktop view (see the "Fly-Left" section below for workaround).

Please view the source of this web page to grab the JS, CSS and HTML markup.

Fly-Left (desktop view)

OK, so sometimes you'll need a fly-out sub-menu from a button that's already close to the right-hand edge of the screen. No problem; a "fly-left" option is provided to reverse the direction of the fly-outs. The " > Previous JemCons" menu is an example of this.

To trigger the "fly-left" menu, just put a class of "fly-left" on the parent <li> tag;

<li class="fly-left"><a href="#">Previous JemCons</a> <label for="jemcon-previous" class="toggle-sub" onclick="">&#9658;</label>
<input type="checkbox" name="nav" id="jemcon-previous" class="sub-nav-check"/>    
 <ul id="jemcon-previous-sub" class="sub-nav">
    <li class="sub-heading">Previous JemCons <label for="jemcon-previous" class="toggle" onclick="" title="Back">&#9668;</label></li>
    <li><a href="#">JemCon 2013</a></li>
    <li><a href="#">JemCon 2012</a></li>

You can actually reverse the fly-out direction half-way through a series of nested sub-menus too, however, please be aware that on iPad (where the desktop menu is also likely to be visible), this can lead to the reversed fly-out sub-menus obscuring parent menu buttons. On a desktop PC this isn't a problem because the menus hide again once you hover away, but on touch-devices, hovers are replaced with clicks/taps so you cannot hover away to hide the menu. The only way to close an opened menu is to click/tap on another link, but you can tap-to-close drop-downs with JavaScript.

Go-Up (desktop view)

Long, nested drop-down menus will eventually fall off the bottom of the screen. To counter this, a "go-up" option is available to change the vertical direction of drop-downs. In otherwords, to make them go upwards. The "Focus on Function > Services" menu is an example of this.

To trigger the "go-up" menu, just put a class of "go-up" on the parent <li> tag;

<li class="go-up"><a href="#">Services</a> <label for="fof-services" class="toggle-sub" onclick="">&#9658;</label>
<input type="checkbox" name="nav" id="fof-services" class="sub-nav-check"/>    
 <ul id="fof-services-sub" class="sub-nav">
    <li class="sub-heading">Services <label for="fof-services" class="toggle" onclick="" title="Back">&#9658;</label></li>
    <li><a href="#">Content Management</a></li>
    <li><a href="#">Graphic Design</a></li>

CSS Transforms and the "position:fixed" bug in Safari

When CSS Transforms are used (as this menu does), there is a known buggy behaviour in Safari that affects child "position:fixed" elements. What happens is that it seemingly forces them to adopt a "position:absolute" placement, while forcing their immediate parents to "position:relative". This causes some very strange (and frustrating) results. There is an easy fix here though - apply this CSS if you have any "position:fixed" elements inside ".container";

/* #### - corrects 'unfixing' bug in Safari - #### */
@media screen and (-webkit-min-device-pixel-ratio:0) { .container { -webkit-transform:none !important } }

Notes (JavaScript enhancement for mobile view)

The first change to the CSS-only version of the responsive menu is that all the checkboxes are named, e.g;

<input type="checkbox" name="nav" id="something-unique" class="sub-nav-check" />

And the bit of JavaScript below is used to remove all checks from checkboxes, thus closing all of the menus/sub-menus at once (in mobile view) instead of needing to close each in turn. I've also included a snippet that adds a class of "js" to the <html> element when JavaScript is enabled which, when coupled with the appropriate CSS, makes the menu degrade back to the CSS-only version when JavaScript is turned off;

document.documentElement.className = 'js'; // adds class="js" to <html> element
function uncheckboxes(nav){
    var navarray = document.getElementsByName(nav);
    for(var i = 0; i < navarray.length; i++){
        navarray[i].checked = false

The JavaScript function is called inside an additional  ×  label, placed just above the closing #menu div, which closes the menu and sends all mobile navigation back off-canvas. Note that there is no "for" attribute in the label - for some reason, things are a bit buggy with it included;

    <label class="toggle close-all" onclick="uncheckboxes('nav')">&times;</label>
</div><!-- closing "#menu" -->

Now to make the new  ×  label look pretty, and also move the large "back"   arrows out of the way, add the following CSS to the mobile area of the stylesheet. Note the ".js" selectors that only apply when JavaScript is enabled;

#menu .close-all { display:none }
.js #menu .close-all { display:inline }
.js #menu .toggle { top:0; z-index:9999 }
.js #menu .sub-nav .toggle { left:0.15em; width:1em }

Finally, hide the  ×  label when in desktop view by adding this to the desktop CSS;

.js #menu .close-all { display:none }

Notes (JavaScript enhancement for keyboard tab support on desktop)

Problem: :hover activated CSS menus can only have their first-level buttons tabbed-to. While sub-menus can be momentarily revealed when a parent receives :focus, they will be hidden again once the user tabs off the parent button (when it loses focus). You can see the lost :focus issue here.

Solution: The JavaScript at play here adds an ".active" class to buttons that have sub-menus, when they receive focus. This class is only removed when the previous or next sibling button is tabbed-to. Note that this menu does not currently include ARIA attributes, which would further improve accessibility for screen readers.

In order for the keyboard tabbing aspect to work, the following changes are in effect from the CSS-only version of the responsive menu;


* IE8 and under does not appear on any mobile device so the fact that the "off-canvas" mobile menu doesn't work there isn't really a problem. IE8 doesn't support media queries either so you can just serve an IE8 desktop stylesheet (with the desktop menu CSS) to those visitors.

Please be aware that touch screen desktop/tablet users have to tap the indicator arrows to open sub-menus, and drop-down sub-menus are likely to obscure page content until another link is clicked (see the iOS 'Sticky Hover' fix).

I haven't been able to test in other browsers/devices but feedback is always welcome. Contact me if you spot anything hinky.



Looking for more freebies for your website? Grab a bunch of free PHP, CSS and JavaScript goodies, from flat file CMS' to RSS managers to responsive CSS menus, galleries and sliders.