The Blog

ADxMenu2 — pure CSS multilevel menus

A new way to accomplish multilevel menus without the Javascript. Peter Nederlof has created a behavior that adds arbitrary :hover support for IE/Win. That means that IE will now recognize li:hover, thus giving us more degrees of freedom for menu creation.

That means that basic functionality does not depend on user’s preference regarding Javascript. You can include the script only if you want to add additional usability features: off-screen positioning check, as well as placing menus over drop-downs (WCH).

I have slightly changed the way CSS is built. All of them now depend on li:hover. Peter’s behavior deals with IE’s lack of support, but only on windows. That means that ADxMenu 2 menus will not work in Mac IE, Opera 6 and any other browser that doesn’t fully support :hover pseudo-element.

Major improvement: this CSS finally solves the “phone-book” problem in IE/Win, and sub menus can be normally repositioned, not one over another.

Pure CSS menus

First rule is to include the behavior (with proper path, relative to the .css file).

body { behavior:url("csshover.htc"); }

Then we remove the default list rendering and reset all box properties to 0. List items are floated to the left and positioned relatively, to define new coo-system for their child elements (sub menus).

#menu ul {
	margin: 0;
	padding: 0;
	border: 0;
	list-style-type: none;
}

#menu li {
	margin: 0;
	padding: 0;
	border: 0;
	display: block;
	float: left;
	position: relative;
}

A elements are transformed to blocks. For the sake of, and only for, IE/Mac, they are also floated to the left, using famous backslash-commented hack. All sub menus are absolutely positioned.

#menu a {
	float: left;/* */float: none;/*  */
	display: block;
}
#menu li ul {
	visibility: hidden;
	position: absolute;
	z-index: 10;
}

The next part is crucial to avoid “phone-book” problem. Sub menu list items are pos:relative for anything but IE. This way, there are no new z-index “spaces” and sub menu will not go under the remaining items in the parent menu. Only when the sub menu is about to be displayed, its parent item is going pos:relative, allowing …

#menu li ul li {
	width: 100%;
	display: block;
	float: left;
	position: static;
}
html>body #menu li ul li {
	float: none;
	position: relative;
}
#menu li ul li:hover {
	position: relative;
}

…correct placement. Without the li:hover support, this is not possible. Peter deserves eternal gratitude for this.

#menu li ul li ul {
	top: 0;
	left: 100%;
}

Unfortunately, there is no way to simulate the child selector, also needed for nested menus. Therefore, we need to cheat a little.

div#menu li:hover ul, #menu li:hover li:hover ul,
#menu li:hover li:hover li:hover ul {
	visibility: visible;
}
#menu li:hover li ul, #menu li:hover li:hover li ul,
#menu li:hover li:hover li:hover li ul {
	visibility: hidden;
}

That first div is needed for IE5.x support. Last version of behavior should actually work without the actual node name specified. And it does in IE6. Also in some simple cases in IE5, but not in this. Strangely, adding just one node name solves the problem. Peter said to me that it must be some error in .htc, but I believe that it’s some IE5.x quirk.

For advanced browsers, that understand child selector, use it instead of the code above. This part can actually be removed, but I deliberately included it to show the proper way how this should be done.

#menu ul#menuList li:hover > ul {
	visibility: visible;
}

That is all. This is enough to make the menus work. Rest of the styles is up to your CSS and designer skills. Take a look at the examples I provided.

Read the next ADxMenu entry to learn about the script that will reposition the menus if they go outside the visible window area. It also incorporates WCH2 script which helps with yet another IE/Win problem.

Update (May 1st): After few questions in the comments, I realized that I haven’t explained the logic behind the IE simulation of child selector. So, here it goes.

How to add more submenu levels for IE

If you look at the unstyled version of the list, you can see that, when particular item is hovered, we want the first nested list to appear, and all deeper nested lists to be hidden. This is possible with child selector – it will take all elements inside of the li and fetch just first ul it encounters:

#menu li:hover > ul {
	visibility: visible;
}

In example list, when mouse is over Products, browser’s CSS engine will find that li and then start to process all elements inside of it. It first finds a, skips it, and then finds ul, applies the style to it and stops.

Since IE doesn’t support this, we need to simulate it. Look at this:

#menu li:hover ul {
	visibility: visible;
}
#menu li:hover li ul {
	visibility: hidden;
}

Look at the HTML code for the list and work it out down the nesting. #menu starts it up and then goes to process all its children. It skips the ul for the main menu, and then finds the Products list item. li:hover ul means “find all UL elements inside of this LI and apply the style to all of them”. And thus not only Products submenu is visible, but also Categories and Hardware. So we need another rule to leave Products visible, and hide those that are nested deeper.

The other rule has one more li before the ul. That means that browser will find all ULs nested inside each list item in the Products submenu and hide them.

To be simple and clear: when you load the page, only main menu is shown. When you mouse over Products item, first rule will show all possible submenus – for Products, Categories and Hardware. What we actually need is just Products submenu to appear. Second rule makes sure that Categories and Hardware submenus are hidden.

For deeper levels, you need to repeat this rule pair and each time add one more li:hover just after #menu. Here is how it looks for one level down (Categories).

#menu li:hover li:hover ul {
	visibility: visible;
}
#menu li:hover li:hover li ul {
	visibility: hidden;
}

Since I have 3 submenus (main menu is not counted), in all .css files I have 3 pairs of rules.

Or, to cut the long story short – number of li:hover is identical to submenu level.

Banca

Banca

Beautiful and functional currency converter, supports just about any currency in the world.

Go Couch to 5k

Go Couch to 5k

The most popular starter running program in beautiful feature-rich app (GPS tracking, charts, detailed history etc)

Quickie to do

Quickie to do

The fastest short-term task-list / check-list app on the App Store. Really.

Guerrilla Cardio

Guerrilla Cardio

The most challenging high-impulse interval training in the world.

Run Mate

Run Mate

A versatile running coach app, with unlimited number of running programs. Perfect for casual runners.

19 Comments

Feel free to chime in, looking forward to it. Leave a Comment

  1. Douglas Clayton says:

    aleck,

    The menus look terrific (particularly the Luna-style ones). I’m really impressed that you can do that with just CSS (in good browsers, at least).

    While I was running them, I noticed one minor issue: if you don’t have a menu on the current page, but still call ADXM_oX.Add() in the header, the ADxMenu.js ends up referencing a null node. Specifically the code on line 126:

    oMenu = document.getElementById(this.aMenuids[i]);

    if (typeof(oMenu) != “undefined”) {

    The problem is that getElementById seems to return null, not undefined (at least on IE6 and Mozilla). I fixed it by adding a test for null:

    if (typeof(oMenu) != “undefined” && oMenu != null) {

    I don’t know if the “undefined” is needed or not (maybe older buggy browsers?) but I left it in just in case.

  2. aleck says:

    Douglas, thanks for the catch. I have updated the code on site.

    I think that simple if (oMenu) will do. I have to check on the Mac but I expect it to work.

  3. Tim Connor says:

    Okay — pure (but .htc for IE) CSS menus with option of WCH in IE (which was what I needed). You can just add the WCH calls into the .htc

    function HoverElement(node, className) {
      if(!node.hovers) node.hovers = {};
      if(node.hovers[className]) return;
      node.hovers[className] = true;
      node.attachEvent('onmouseover',function() {
        node.className += ' ' + className;
        if(node.className.indexOf('hasHider')!=-1){
          WCH.Apply(node.id+'Hider',node);
        }
      });
      node.attachEvent('onmouseout',function() {
        node.className = node.className.replace((new RegExp('\s+'+className)),'');
        if(node.className.indexOf('hasHider')!=-1){
          WCH.Discard(node.id+'Hider',node);
        }
      });
    }
    

    <ul id=“topMenu”>
    <li class=“hasHider” id=“menu”><a href=”#”>Menu
    <ul id=“menuHider”>
    <li><a href=”#”>Menu Item 1
    <li><a href=”#”>Menu Item 1
    <li><a href=”#”>Menu Item 1
    </ul>
    </li>
    </ul>

    I’m trying to figure out if I can think of a more elegant way to do it, but, so far, this is the best blend of flexibility, performance, and simplicity, that I can think of.

  4. Hi,

    is there any way to avoid the menu disappearing behind a select box?

    Best Regards
    Koen

  5. Only happens in IE though, forgot to mention in last post, sorry.

  6. aleck says:

    Koen, you just need to use additional scripts for that. Read here about the techniques.

    Tim, that’s nice idea. Koen, you might want to check Tim’s integration of WCH into pure menu examples.

  7. Jay says:

    Aleck,

    I have noticed a strange bug in the “basic” pure CSS menus.

    You can select a list item anywhere you hover for the first item in the list, or if the item is a submenu. However, for others, the click must occur on the actual word/anchor.

    What makes this more strange is that this bug does not appear in the “minitabs” or “winxp” versions.

    Do you have any suggestions to fix this?

    Thanks,
    Jay.

  8. Aleksandar says:

    Jay, this happens in IE, right? :)

    Problem is the way IE work when you do a {display:block}. It will expand that block as wide as possible (which can be seen if you set background-color), but it will not expand the clickable area, unless you explicitly set the width to something.
    You can read more about it in Doug Bowman’s Sliding Doors article at ALA.

    First value that come to mind is 100%, which is fine if you don’t have paddings (when IE6 will draw wider menus). In minitabs and winxp I have explicitly set the width, while in basic I have not, hence the click problem.

    (rant: 100% problem would not exist if it wasn’t for the highly unpractical W3C box model)

    Now, the mistery here is why IE extends clickable region for first item in the menu, but not for others…

  9. Jay says:

    Aleck, yes I forgot to mention it was IE! ;)

    Thanks for the tip to get things working. After some trial-and-error, I managed to fix it by adding the following to menu.css:

    #menu li li a {
    width:95px;
    }

    (Change the 95px to a size appropriate for your own menu. As you indicated, specifying 100% was not quite right, as it pushes everything too wide due to parent elements having padding).

    Maybe CSS3 will solve some more headaches… whenever it is adopted. :)

  10. Henrik Nyh says:

    Hi. I love the menu.

    I noticed that using IE, on some of my pages the menus wouldn’t open.

    After a lot of testing this and that, I realized it was because the location of csshover.htc in

    body { behavior:url(“csshover.htc”); }

    is not relative to the location of menu.css, but rather to the page using menu.css.

    That is, if I have

    /csshover.htc
    /menu.css
    /subdirectory/page.html

    then even if page.html uses menu.css for an external style sheet, perhaps linked absolutely, the menus on it won’t work in IE because it tries to get csshover.htc relatively.

    I fixed it by making an absolute path to csshover.htc.

    Thought I’d share, if anyone else runs into this.

  11. Douglas Clayton says:

    Jay,

    I noticed the same problem. In my case, I fixed it by adding “position: relative” to the “#menu a” style. It makes the clickable region extend out to the end, without worrying about percentages or any other hack. As far as I know, it doesn’t cause problems for other browsers (I think it’s acceptable to have–the only problem I could think of is that it changes the cascading nature of absolute positioning).

    On a related note, what is the reason for this line:

    float: left;/* \*/float: none;/* */

    What browser is getting the float: none and why?

    Thanks,
    Doug

  12. Aleksandar says:

    IE5 / Mac gets float:left, all others get none. The reason is explained in Sliding Doors article (mentioned above).

  13. Jason Fisher says:

    IE6/Win isn’t caching icons and images (ie. arrow.gif) — all examples, including Luna with pure CSS, run perfectly from local disk, however it seems that every time I highlight an entity of a menu it reloads/redraws all icons within that menu — VERY slowly.

    Any ideas? Maybe I haven’t dug deeply enough around here to find the answer.

  14. Aleksandar says:

    No Jason,you are right. IE shows its ugly side anytime you try to do something really good with CSS.

    There are numerous ways people tried to avoid that. One that is very simple and works good is to place background image on both A and LI element, if that is possible (first done by Todd Dominey, if I remember correctly). For some reason, flickering is not obvious then.
    Ironically, if you look at it in IE and files are served over Apache, the problem is not there. But when pair-up two MS‘s own products, it flickers. This is not a first time that I have problem on relation IIS-IE, but not on IIS-any other browser. Really amazing. :(

    In the projects I did so far, this presents itself as a problem only when I have a page loaded over SSL. Even on cable, the flickering then is noticeable. In regular HTTP, I didn’t have any complaint. And I do have some picky customers ;).

  15. Tim Connor says:

    Yes, if you are going to do ssl, it WILL flicker, no matter what sort of fancy css tricks you use. I HATE IE. Grrrrr.

  16. Cat says:

    Hi,

    The menus are wonderful, but I am having one problem. I can no longer use a:hover for any other links. I used to have my links underline when the mouse hovered over them, but
    now either the links will be underlined, and the menus will break (hovering over them changes the top menu color, but no menus drop down), or the link doesn’t underline. This is happening in all browsers (Mozilla Firebird, IE, Opera, Netscape).

    I’ve been trying setting a class=“links” for the link (in the table cell) and then .links a:hover {text-decoration: underline;}

    That isn’t working. Is it not possible to have the hover feature working outside of the css menus?

    I’ve tried a variety of syntax, but am not as advanced with CSS as I’d like.

    Am I doing it wrong, or is it not possible to use hover in conjunction with these menus?

  17. Cat says:

    Just wanted to followup and say that I made a mistake. The:

    a:hover {text-decoration: underline;} works fine with all browsers except Opera. Since the menus had worked so nicely with every browser including Opera, when I was testing the hover feature for links, I failed to check if the error was happening in other browsers.

    If anyone knows a fix for Opera, that would still be very helpful!

  18. john says:

    I am in the process of updating my school’s website, aiming to improve accessibility by using a P.I.E. (BigJohn) layout and your fantastic menu — the first stages are at http//:www.compusmall.co.uk/pie/index.php. I came to the menu via Eric Meyer’s new book. The problem is with IE on the Mac. Does it simply not work or is there a fix or any hope of one? I am a ‘copier and paster’ (too old to start from scratch!) but would welcome any pointers in the right direction or suggestions for mac/ie users. One idea I have is to have the links on the first level menu, which does show up, referenced to a site map page; hopefully non-mac/ie users would see the menu open up, not click on the first level but follow the hover, but I am not convinced!

    Thank you, John

  19. Aleksandar says:

    John, this is not working in IE/Mac, as noted at the beggining of the entry. For it, you need to use 2b version of the script, as explained in csshover.htc and IE freeze entry.
    That version is using javascript for all IE (Win and Mac) so it can’t be cold (semi-)pure CSS menus. :)

Comments are now closed for this article.