CSS Pop-Out Menu Tutorial CSS MENUS - FLUID AND ACCESSIBLE - VERTICAL TUTORIAL What follows is an example of how to develop menus similar to the ones shown here and here. This tutorial uses very similar code to cover the main ingredients for both those menus, they can be developed further by adding background images or more color. There is a technical introduction covering both versions on this site, for those that want to know more about the "hows and whys", otherwise we'll just get started. REQUIREMENTS FOR BOTH MENUS Grab the HTML Download the whatever:hover file from Peter Nederlof's site. Forget about Internet Explorer, IE, until I tell you to Remember Please view the interim samples in a CSS compliant browser (Firefox or Opera), not IE again until I say so ;) Note: Anything we do from this point on will not affect text browsers or non CSS parsing applications. It's CSS, it will only affect the visual presentation. VERTICAL POP OUT MENU STRAIGHT TO THE CSS! #menu { width: 12em; background: #eee; } I have applied, in em's, the width of the menu to the "container" div ~ <div id="menu">, that way we can forget about IE's Box Model problems as all block elements inside this will naturally fill the container's width by default, even if borders are used on internal elements. em widths are used to enable the menu to resize to contain text if/when text is enlarged. And for the purposes of this demo I have added a background color to this div for visual reasons only. Now that the list, <ul>, elements are all contained. The first thing we do is remove the Bullets and Indents which browsers naturally apply to <ul> elements by default. Both padding and margin require to be set to zero as different browsers create the indent using either of these properties. #menu ul { list-style: none; margin: 0; padding: 0; } Then we would like to make the <h2> headings and the <a> anchors appear in the same size text and with borders but with slightly differing colors to differentiate between the two. This is also where we'll add any color, background changes to the anchor text on hover. #menu a, #menu h2 { font: bold 11px/16px arial, helvetica, sans-serif; display: block; border-width: 1px; border-style: solid; border-color: #ccc #888 #555 #bbb; margin: 0; Pg 1 of 5 padding: 2px 3px; } #menu h2 { color: #fff; background: #000; text-transform: uppercase; } #menu a { color: #000; background: #efefef; text-decoration: none; } #menu a:hover { color: #a00; background: #fff; } View results of stage one, remembering to view with a non-IE browser please. POSITIONING THE POP OUTS Now we want to move the third level nested level lists out to where they should be, to their "pop out" position. The way we do this is to use Absolute Positioning which takes them out of the flow of the page and allows us to position them where we want. #menu ul ul ul { position: absolute; top: 0; left: 0; } In this demo code we don't want to move the first level list (which contains the [h2] element) nor the second level lists (top level choice) to a pop out position, so we need to use CSS specificity to select only <ul> elements that already have at least TWO parent <ul> elements. This now looks like this example, remember to view with a non-IE browser. That has now moved the required lists, taken them out of the flow of the menu, but there are two things wrong. 1. 2. They are not positioned where we would like them to be. They are not the correct width any more Both problems are overcome together. ABSOLUTELY POSITIONING, RELATIVELY SPEAKING These lists, when they were in the flow of the menu (in the default "static" position), appeared in the flow, and took their inheritable properties from their parent elements. However now we have removed them from the flow using Absolute Positioning they have no parent element, instead they have a containing block. So the "pop-outs", at the minute are positioned at top: 0; and left: 0; of their Initial Containing Block (the viewport or <html> root element). You don't need to read all that if you don't want. All we really need to know is: If the element has 'position: absolute', the containing block is established by the nearest ancestor with a 'position' of 'absolute', 'relative' or 'fixed... ...If there is no such ancestor, the containing block is the initial containing block. For Our menus we establish new containing blocks for these popout <ul>'s by adding position: relative; to these <ul>'s nearest ancestors, which is always an <li> element. Then because using position relative with no offset coordinates specified does not affect the actual position of an element we do not need to single out only affected <li>'s we can apply it to all. Pg 2 of 5 For the width correction, Absolutely Positioned Elements need to be explicitly told what width to be. If you set the width to be 100% they will become 100% of their containing block, and because we're going to establish new containing blocks for them we can safely tell them to use percentage widths again. This CSS below has now corrected the width and positioning, trust me it has, but now the "popouts" are appearing on top of the first level lists because both their coordinates were set at zero we need to adjust the left co-ordinate to move the popouts over to the right side of their containing block. Lets set them to 100% left to avoid having to do any sums with widths again. #menu li {position: relative;} #menu ul ul ul { position: absolute; top: 0; left: 100%; width: 100%; } This now shows all the popouts in their correct position, remember to view with a non-IE browser. The Popouts are all there in the right position it's just that some of them are overlapping vertically, that's OK that's exactly where they should be as you will only be seeing one of them at a time when their display is toggled by the hover. HIDING AND REVEALING USING :HOVER First let's hide all those "pop out" menus using the display property. A "Remember IE" Moment This is where we have to get more specific with the CSS than we should have to be because of IE. We cannot use Child Selectors so we will do it longhand, it's OK, this way will still work for other browsers too. Also due to working with an older csshover.htc file while developing these menus we became aware that IE5.x required a more specific element selector or the menus just didn't work in those browsers. The behavior file has now been updated to address this issue, so using this fix is no longer required. Although a point to note: In our testing/development of these menus we found that leaving the CSS workaround in place improved IE's performance on larger menus. The choice is yours, I'll leave it in this demo CSS. I am going to put this into a separate CSS Rule because it's easier to do the final bit then, and it keeps the IE workaround CSS visually separate, you'll see at the end. So let's hide all the popouts div#menu ul ul ul {display: none;} Easy eh.. Now to get them re-appear we want to declare that when a second level <li> element is hovered over we want it's child <ul> to appear. div#menu ul ul li:hover ul {display: block;} This now looks like this example, try the hovers, remember to view with a non-IE browser. So that's cool, except that you probably don't want all of the child <ul>'s appearing until you hover over their immediate parent <li>. This is where we could do it easier with child selectors, but we can get around this using a bit of longwinded CSS. So we leave what we have but now tell it that we want to hide any <ul>'s that are two or more descendant children of the hovered <li> - Yikes! So we take the rule that we just used to display the lists div#menu ul ul li:hover ul and add a second <ul> to the end of it making, div#menu ul ul li:hover ul ul and add that to the display: none; rule. Pg 3 of 5 div#menu ul ul ul, div#menu ul ul li:hover ul ul {display: none;} div#menu ul ul li:hover ul {display: block;} This works, we get one level visible now on the hover, but when we move over to the popout and hover on it the deeper levels are remaining hidden. This is because the number of element selectors we now have in the rule we just added to the display: none; rule is overriding the selector in the display: block; rule as per CSS Specificity Rules. To Counteract this we need to add much more specific rule to the display: block; rule too. div#menu ul ul ul, div#menu ul ul li:hover ul ul {display: none;} div#menu ul ul li:hover ul, div#menu ul ul ul li:hover ul {display: block;} This now achieves what we want, in a non-IE browser. You would then need to keep adding similar counteracting selectors to each of these two rules, as necessary, depending on how many levels deep your menu pops out. But that is all the levels we need for this demonstration code. NOW IT'S IE'S TIME! You can now take a look at the link above with IE, it's not pretty. As with everything CSS it's always better to develop in a non-IE environment and then go back and "fix it" later. Because all of IE's foibles are well documented, the good news is that the majority of IE differences can be worked around with Pure CSS. WHAT'S WRONG WITH THE IE PICTURE 1. 2. 3. 4. hovering does not activate the pop-outs there's whitespace between the list items the anchor hover background change effect only works if the link text itself is hovered over, it should happen on the whole block. you cannot increase or decrease the text size The First thing to do is to put a conditional comment in place and call the Magic Ingredient, the csshover.htc file. Everything we do just for IE alone will go inside this conditional comment as the other browsers will ignore it and we need not interfere with them any more because it's working properly for them. <!--[if IE]> <style type="text/css" media="screen"> body {behavior: url(csshover.htc);} </style> <![endif]--> Providing the csshover.htc file has been saved to the same directory as the file you're working on you should see that the pop outs are appearing on hover as expected. View this page with Internet Explorer 5/6 for windows, for confirmation That's problem #1 dealt with thanks to a copy/paste solution. FIXING IT FOR IE! Pg 4 of 5 Next, Whitespace in lists is a common IE occurrence and is related to many other IE display errors (that's for another day though), and although IE5.x and IE6 have slightly different causes for this, one fix fits all #menu ul li {float: left; width: 100%;} #menu ul li a {height: 1%;} Adding the above inside the conditional CSS fixes the "whitespace in lists" problem across all versions of IE and also makes the anchor into a proper block so activating the hover, background color change properly. Lastly we want to deal with text resizing problem. IE won't resize font's that are declared in pixels but it will if they are declared in em's or percentages, providing the initial body font-size is set to a percentage itself. So add fontsize: 100%; to the body rule in the conditional CSS and then convert the font-size we set on the #menu a, menu h2: #menu a, #menu h2 { font: bold 11px/16px arial, helvetica, sans-serif; } converted to em equivalent #menu a, #menu h2 { font: bold 0.7em/1.4em arial, helvetica, sans-serif; } Now add the em version into the conditional CSS too (don't replace the pixel version in the main CSS!), The completed conditional CSS is: <!--[if IE]> <style type="text/css" media="screen"> body { behavior: url(csshover.htc); font-size: 100%; } #menu ul li {float: left; width: 100%;} #menu ul li a {height: 1%;} #menu a, #menu h2 { font: bold 0.7em/1.4em arial, helvetica, sans-serif; } </style> <![endif]--> The reason I have left the original font-sizing CSS set in pixels and only converted it to em's for IE is that compliant browsers will produce different "rounding calculations" for em values which could cause gaps between the list items when pixel borders/padding are used. Most non IE browsers will happily resize pixel fonts and their border/padding more accurately, so it is a better option in this case to continue to use pixel font sizes for them. IE doesn't have such an obvious "gapping" rounding problem, so the font-resizing workaround can be safely be put in place for it especially if the text resizing capability is a more important feature of your menu. The complete code now looks like this, you can now view in all browsers and admire your work. Pg 5 of 5