CSS3 Multi-Level, Off-Canvas Mobile Menu (with JavaScript IE7/8 support)

The CSS-only version of the menu has been further enhanced so that it now supports IE7/8, thanks to a dash of JavaScript. JavaScript also provides a function to close all sub-menus, whereas with the CSS-only version, all opened sub-menus have to be closed in turn. Please read the Notes section for details about JavaScript enhancement.

There is another version of this menu that doesn't support IE7/8 - it also includes the "close all sub-menu" JavaScript and may be useful to you if you plan on having a traditional, visible desktop menu for IE7/8 users.

See that   / 3-line-stripy / 'hamburger' icon in the top-right corner? Good! Now go click/tap it...


A CSS3, "off-canvas" menu that;

* JavaScript is required for IE7/8 support, although there is a <noscript> workaround.

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)

Like the earlier version of the menu with JavaScript enhancement, this new IE7/8 supporting menu also includes the function that closes all open sub-menus. I won't cover that again, but if you're interested, go and take a look before continuing here.

Now, because this menu builds upon the CSS-only version, it continues to use the checkbox hack, which is what makes functionality possible without JavaScript, in modern browsers. But, the checkbox hack doesn't work in IE8 and earlier because the ":checked" psuedo class is unsupported in IE before version 9. So, for IE7/8 support, we must do something different - I decided to toggle a class on active menus, styling them open and closed, when needed, with CSS.

Here's the goNav() JavaScript function that toggles the additional class. "sub-nav" is the default class that is always present, and "nav-ie" is an additional class that is added and removed onclick;

function goNav(nav){
    document.getElementById(nav).className = (document.getElementById(nav).className == 'sub-nav nav-ie') ? 'sub-nav' : 'sub-nav nav-ie';

You can see how this "nav-ie" class is used in the CSS, inside an IE conditional stylesheet. Remember that CSS transforms are not supported in these early versions of IE, so they get a basic left position switch - no slinky slide effect, but at least the menus are made visible (they are initially hidden "off-canvas" with a negative left position of -13.75em);

<!--[if lt IE 9]>
#menu ul { margin-left:0 }
#menu.nav-ie, #menu .nav-ie { left:0 }
#container.nav-ie { position:relative; left:13.75em }

So, when the "nav-ie" class is present, the sub-menu is made visible, and when it is removed, the sub-menu is hidden again.

The rest of the JavaScript ties the goNav() function to all the sub-menu's "toggle  and "toggle-sub  arrow labels so that the "nav-ie" class is added and removed onclick;

var sub = document.getElementById('menu').getElementsByTagName('label');
for(var i = 0; i < sub.length; i++){
    if(sub[i].className == 'toggle-sub'){ // forward '>' arrow label
        sub[i].onclick = function(){
            var chkbox = this.nextSibling;
            while (chkbox.nodeType != 1){ chkbox = chkbox.nextSibling; }
            var subNav = chkbox.nextSibling;
            while (subNav.nodeType != 1){ subNav = subNav.nextSibling; }
    if(sub[i].className == 'toggle'){ // back '<' arrow label
        sub[i].onclick = function(){

Finally, the existing uncheckboxes() function that closes all sub-menus in one go in modern browsers, now carries a bit of extra JavaScript to remove all instances of the "nav-ie" class, which again, closes all sub-menu in one go, but for IE7/8;

function uncheckboxes(nav){
    var navarray = document.getElementsByName(nav);
    for(var i = 0; i < navarray.length; i++){
        navarray[i].checked = false

    var navIE = document.getElementById('menu').getElementsByTagName('ul');
    for(var i = 0; i < navIE.length; i++){
        navIE[i].className = navIE[i].className.replace(/\bnav-ie\b/, '');

That's the sub-menus dealt with. To completely close the menu and return it to it's hidden, "off-canvas" state, I'm going to call the goNav() JavaScript function on the  ×  labels - there's one right after the opening #menu div, and another just before the closing #menu div - they'll end up looking something like this;

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

But wait! It's all well and good talking about how to open and close sub-menus in IE7/8 with JavaScript and classes, but I haven't actually yet covered the very first step of opening the menu from its "off-canvas" position! That's very easy to handle when JavaScript is enabled - just put the goNav() function on the   icon;

<label for="main-nav-check" class="toggle" onclick="goNav('menu'); goNav('container')" title="Menu">&#x2261;</label>

Problems arise when JavaScript is disabled though - those IE7/8 users won't be able to access the menu at all, so I propose this workaround; Use <noscript> tags to serve a static link to your website's sitemap;

<label for="main-nav-check" class="toggle" onclick="goNav('menu'); goNav('container')" title="Menu">&#x2261;</label>
<!--[if lt IE 9]><noscript><a class="toggle" href="/site-map.php">&#x2261;</a></noscript><![endif]-->

And mimic the existing   icon with this CSS in the IE7/8 conditional styles;

#header a.toggle { padding:0 0.125em; font:2.875em/1.4375em Arial; text-decoration:none }


* See workaround above for <noscript> tags.

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.