CSS3 Multi-Level, Off-Canvas Mobile Menu (with JavaScript enhancement)

This version of the menu is a progression of the CSS-only version and uses only a teeny bit of JavaScript to close all the menus/sub-menus in one go. This is a bonus over the CSS-only version where you have to close all opened sub-menus in turn. Please read the Notes section for details about JavaScript enhancement.

Give that   / 3-line-stripy / 'hamburger' icon in the top-right corner a click to start things rolling...


A CSS3, "off-canvas" menu that;

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

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)

The first change to the CSS-only version of the 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 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 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 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 }


* IE8 and under does not appear on any mobile device so the fact that this 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 (and IE8 menu) to those visitors. Alternatively, try this version of the multi-level, off-canvas mobile menu that works in IE7/8.

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.