- Ruthsarian :
- Layouts :
- Labs :
- Blog :
- Contact :
March 02, 2005
CSS-Based Drop-Down Menus
CSS-based drop-down menus aren't anything new. Suckerfish got
the ball rolling. Soon after we got Son of Suckerfish
which is the basis for a lot of the content in this post. I'll be going off on my own attempts at drop-down menus and try
to share a little bit of insight gathered from the experience. Caveat: I'm throwing embedded stylesheets into this
post to generate the drop-downs. Typically you don't put STYLE blocks into the body of a webpage. But, hey, it's
not like this version of MT has been extremely tight on its compliance with w3 standards. :) So here we go.
The concept is simple enough. You create a nested list (ordered or unordered, it doesn't matter, though unordered is my preferred choice) and hide all sub-lists so that only the top level is displayed. When a user mouseovers a list item, the sublist it contains is displayed on screen. When the user mouseouts the sublist disappears.
This is achieved in CSS by making use of the :hover pseudo-class. Let's say we have a list with the id
attribute set to nav. Take a look at the following CSS:
#nav ul { display: none; }
#nav li:hover ul { display: list-item; }
So the top-level list (#nav) has all it's sub-lists (UL) hidden (display:none). When a
list-item (LI) is in a hover state (the mouse is over the item) the sub-list of that list-item is then displayed
(display:list-item).
I use list-item rather than block for the display attribute because that's what the
default value is for lists, and we should try to stick with these standard values as much as possible.
I won't bother with an example of the above CSS. It doesn't look great, doesn't work in IE, and seems to generate artifacts in FireFox. Basically what happens is you get a list with all top-level items displayed. If you mouse over one of the items, all lists contained within that list element are displayed. So if you've got a nested list 4-levels deep, you'll see all 4 levels appear when you mouse over the top-level element.
The two key problems here are that it doesn't work in IE, and mousing over an element reveals all sub-lists. What we want is for the drop-downs to work in IE and when you mouse over an element, to only get the next level down to display, and keep any lists 2-levels below hidden.
We can take care of the second part first, under FireFox and Opera and basically all other modern browsers but IE, by making use of a combinator that allows us to specify only immediate children of a given element. This combinator is '>'.
Making the appropriate edits to the previous CSS we get the following:
#nav ul { display: none; }
#nav li:hover > ul { display: list-item; }
Not a big change at all, but it makes all the difference in the world. With the added combinator (that's the w3's term, not mine) only the immediate (next level down) sub-lists get displayed. This means you won't see lists 4-levels deep when you mouse-over a top-level element. You'll only see the next-level down.
Nice and simple, isn't it? Just add some "window dressing" and you get this:
#dropnav, #dropnav ul
{
margin: 0;
padding: 0;
list-style: none;
width: 10em;
}
#dropnav li ul
{
position: absolute;
display: none;
margin: 0 0 0 65%;
}
#dropnav li:hover > ul
{
display: list-item;
margin-top: -0.5em;
}
Which generates something that looks like this (sans the color, borders, and ... okay so it doesn't look like this, but it functions like this:
Looks pretty cool, eh? Oh, wait, what's that? You're using IE and when you mouseover nothing happens? Tis true, IE has two
issues (well, more like a million, but 2 that we care about for this article) that prevent this from working. First
is that it doesn't support the :hover pseudo-class on anything other than anchors. Meaning it doesn't understand
li:hover and ignores the selector. Furthemore, IE doesn't support the > combinator. This will cause problems
that we'll address later. First let's see what can be done about this :hover business.
Simple. We fake it. How? Javascript. Now there are other methods where you wrap the sub-lists inside an anchor tag, but that creates a really nasty mess. Besides, the HTML spec doesn't allow nested anchor tags, which is what you'll have if you got more than 1 level deep. So that's out. We could just hide the sublists for IE users and make sure that alternate navigation methods exist for IE users when they click on the top-level item. (That is, assuming you're using drop-downs for navigational elements on you website. That's the typical use, but maybe you will have an alternative.) This method is nice in that you don't have to resort to Javascript and you can have really tight CSS. But chances are you want IE support. So we return back to Javascript.
I won't get into the dirty with Javascript, but you can check out the Son of Suckerfish webpage where I shamelessly stole the Javascript from. I made only a slight modification in that the element id is passed as a function argument rather than hard-coding it. This allows for 1 function to support multiple drop-downs on a single page. You can view the source of this page if you care enough about the Javascript, but I'm trying to keep short and already very long post.
What the Javascript does is simply set a function to trigger whenever there is a mouse over an LI element.
That function changes the class attribute for that element to include the class sfHover. On mouseout,
another event triggers to remove this from the class attribute. (Remember, class attribute can hold more than 1 class, each
class name being separated by a space.) So now we can fake li:hover by using li.sfHover for IE.
Now comes the really tedious part. IE doesn't support the > combinator. So how can we keep only the immediate sub-list displayed and all deeper sublists hidden when the user mouseovers an element?
#nav li.sfhover ul ul, #nav li.sfhover ul ul ul { display: none; } #nav li.sfhover ul, #nav li li.sfhover ul, #nav li li li.sfhover ul, { display: list-item; }
I'll let that soak in for a second.
By nesting ul and li elements we can target specific levels to block any lists below it
from displaying when they are being hovered. What's tedious about this is you have to keep adding these selectors
in for every level of depth you add to your drop-down menu. This example covers three-levels.
Now I don't like this for two reasons. One, as stated
previously, it's tedious. The other is that I don't feel we should have to worry about how deep a list goes. Any
solution should be scalable to N-depth without the need for changing the CSS. Maybe there's a solution with
Javascript, which I might try to work out myself, but for now this is what we got. I don't like it at all, which is why I'd
seriously think about just ignoring IE users. (But I suppose we can't really do that, can we?)
But when all is said and done, you wind up with something like this:
Now even IE users can use the drop-down menu. Very nifty.
There are, of course, a couple of silly little IE bugs that I had to workaround to get the drop-downs working well under IE6, mostly holly hack stuff, but it's all working nicely.
Except...
What's missing? No links. No anchor tags in the examples that I've shown up to now. I've done that on purpose so it was one less thing to focus on/worry about while getting the basics down. Now, to really finish this off, we need to add links.
Nothing big here. Just wrap the text in each list item with an anchor tag, point it
at the URL you want it to link to, and you're pretty much good to go. However, you
probably want the link to fill up the entire space the list item takes up; have the
whole box, rather than just the text, be clickable. Well we can do that, but we need
to make the anchors block items first. A simple display: block will
take care of that. Block items will, by default, take up as much horizontal space
as is available, so defining a width on the anchor elements isn't needed. I've
specifically set to underline the link text just so you know what is and what
isn't a link among all the demos here; you will want to probably take that part
out if/when you use this.
So without further adieu, here is the finished, 3-level deep, CSS-based, drop-down menu.
Woo. Hoo.
Okay. So now you want to get your hands dirty and try this yourself. But you don't want to sift through the spaghetti markup MT generates (which you see by viewing the source of this page). Well then, I've put together a little page which has the final demo fo the drop-down list along with all relevant CSS and Javascript to get your own going. You can access CSS-based drop-down demo page here.
So now this example is geared torwards displaying the top-level (always visible)
items in a vertical fashion. You could do run them horizontally, if you wish, by
having top-level items with display: block; and float: left;
set to them. And then maybe tweak the margin values for UL elements
below the top-level so they pop-up where you want them to. I'll leave that as an
exercise for you all.
Good luck.
Post a comment