CSS-Based Drop-Down Menus

ul,
#navdrop li:hover > ul,
#dropsavvy li:hover > ul
{
display: list-item;
margin-top: -0.5em;
}
#dropnav li.sfhover ul ul,
#dropnav li.sfhover ul ul ul,
#dropsavvy li.sfhover ul ul,
#dropsavvy li.sfhover ul ul ul
{
display: none;
}
#dropnav li.sfhover ul,
#dropnav li li.sfhover ul,
#dropnav li li li.sfhover ul
{
left: 0;
display: list-item;
margin-top: 0.75em;
}
#dropsavvy li.sfhover ul,
#dropsavvy li li.sfhover ul,
#dropsavvy li li li.sfhover ul
{
left: 0;
display: list-item;
margin-top: -0.5em;
}
#dropsavvy li a
{
display: block;
width: auto;
text-decoration: underline;
}
* html #dropsavvy li a
{
height: 0.01%;
}
–>

<!–
sfHover = function( objID )
{
var sfEls = document.getElementById( objID ).getElementsByTagName( “LI” );
for (var i=0; i

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:

  • Level One

    • Level Two

      • Level Three
      • Level Three
      • Level Three
    • Level Two

      • Level Three
      • Level Three
      • Level Three

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:

  • Level One

    • Level Two

      • Level Three
      • Level Three
      • Level Three
    • Level Two

      • Level Three
      • Level Three
      • Level Three

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.

0 thoughts on “CSS-Based Drop-Down Menus

  1. When I use the dropsavvy stuff on skidoo_too, the sub-menus pop out underneath content in the middle column. The middle column has a background image.

  2. I tried removing the background image. The drop-downs then extended past the left border of the middle column but hid underneath the images I have on the page. The strange thing is that when a drop-down pops out, the cursor is deactivated after it passes over the right margin of the left column. Another strange thing is that when the page first displays, part of the right border of the middle column (or is it the left border of the right column) is invisible and as soon as I move the cursor over a link in the left column, the border segment reappears. Huh? I think all the problems I’m having is related to the left column.

Leave a comment