Demo : Download : Updated 2007.06.08
In the beginning there was pure CSS menus and it was good. Except it wasn't. IE6 and earlier, as well as other browsers, didn't support the kind of CSS selectors used to generate dropdown menus. And lo we found Suckerfish and all were opened to the power of the dropdown menu.
But Suckerfish didn't work so well either. Son of Suckerfish came along and made things a bit better but it didn't work in IE/Mac and the CSS was still problematic on certain platforms. And so I set about my own journey to create hyper-compatible CSS-based dropdown menus.
Why bother? JavaScript-based menus already exist that provide similar functionality. Furthermore some offer broad compatibility. So why bother trying to create a CSS-based menu? Rather than storing links in some JavaScript array you can use HTML lists. This allows you to create a logical structure and hierarchy in your menu and, subsequently, a kind of logical outline (a summary even) of your website.
There's also the issue of malicious JavaScript. This is a problem that's only going to grow as AJAX or "Web 2.0" type websites become more prevalent. The response will be to disable or selectively allow JavaScript execution. This increases the likelyhood of users not executing the JavaScript your website utilizes. If critical navigation functionality is lost because a user isn't using JavaScript that user won't be able to navigate your site and will leave in search of another.
From a more abstract perspective it's better to provide content (and the means to navigate it) in the most compatible manner possible. JavaScript requires a JavaScript engine or interpreter. It's a common/fair assumption that your users will support JavaScript in some form given the vast majority of modern browsers have it, but there are still a large number of users that don't. Think text-based browsers. Think screen readers for the blind. Think search engines, web crawlers, screen scrapers, and other non-human users that try to access your information. If you rely on JavaScript to use your site these user groups will not be able to access your information.
And so here it is. A dropdown menu that uses basic HTML to provide the broadest level of accessibility/usability possible, but allows for an enchanced, more efficient user interface for clients that support CSS.
Lists. Ordered or unordered it doesn't matter, although I prefer unordered since it's doubtful the structure of a website should be prioritized in such a way. The HTML looks something like this:
<ul>
<li>Menu Item
<ul>
<li>sub-menu Item</li>
<li>sub-menu Item</li>
<li>sub-menu Item</li>
</ul>
</li>
<li>Menu Item</li>
<li>Menu Item</li>
</ul>
Here we see nested lists. The top-level will always be visible to the user.
The nested lists we want to keep hidden until the user mouses over the menu item
that contains the nested list. To do this we use the CSS pseudoclass :hover
like so:
li ul { display: none; }
li:hover ul { display: block; }
This hides all nested lists until a nested list's parent list item is being hovered. An immediate problem you'd find with this CSS is that all the nested lists, including those 3+ levels deep, are displayed when you mouse over a top-level list item containing nested lists. Typically you want only the first list, the one immediately descended from the list item that is being hovered, to appear. This we can do in CSS with child selectors. This allows us to target immediate descendants (children) of a given element, but not further descendants (grandchildren, great grandchildren, etc..). The revised CSS becomes this:
li ul { display: none; }
li:hover > ul { display: block; }
And that's it. That's the core idea, the core CSS to implement CSS-based dropdown menus. Everything else is making the menus look "pretty". Pretty simple stuff, isn't it?
Unfortunately it isn't so simple. IE6 and earlier versions don't understand the :hover
pseudoclass for anything other than anchor elements. The CSS given above just won't work. It'll work
well enough. Nested lists are hidden, only the top-level is visible. You might argue that's good
enough, that dropdown menus are a convienence and not critical to the navigation of your website. That
could very well be true and if you choose to take that route, you're ready to create your own menus.
If you've decided to push for IE6 compatibility you're in luck, it's
already been done for us, but it requires
JavaScript. "Oh ho! You anti-JavaScript jerk, what's this? Eating crow are we?" Well yeah, a little. What can
be accomplished without JavaScript is "enough". Your IE users will see a top-level menu; they'll be able to navigate to
subpages on your site. From those subpages they can go to the deeper levels available to clients that do
support :hover
. So using JavaScript enchances, but is not critical to, the functionality of the
site. That's the key concept here: the use of JavaScript should not be critical to the use or functionality of
a website.
The JavaScript basically adds an extra class to the class attribute of the list item that is being hovered. By adding
this class you can add extra rules to your stylesheet targetting this class to display UL elements. Suckerfish uses
the class sfhover
. So the updated CSS to target this class becomes:
li ul { display: none; }
li:hover > ul, li.sfhover ul { display: block; }
Still have a problem though. IE6 and earlier do not support child selectors. That little greater-than symbol
between li:hover
and ul
actually causes IE6 to ignore the entire selector and rule
completely. So first we have to put the li.sfhover ul
on its own, then we need to add more CSS
to mimic the child selector functionality. The CSS starts to get redundant and you have to add more for each
level of depth you want to support. Below is the final CSS which supports up to 3 levels deep.
li ul { display: none; }
li:hover > ul { display: block; }
li.sfhover ul ul, li.sfhover ul ul ul { display: none; }
li.sfhover ul, li li.sfhover ul, li li li.sfhover ul { display: block; }
Well that's annoying, but we've done it! CSS-based dropdown menus that can also
work in IE 5.0, 5.5, and 6.0. IE 7 actually supports both the :hover
pseudoclass and child selectors, meaning our original CSS will work. Meaning, in
a year's time, it'll be even more acceptable to just drop this whole
JavaScript nonsense completely. Joy of joys!
So you're off. You've stopped reading and started making your own dropdown menus. Even if you haven't and you've got some free time I highly suggest you do. It's a good exercise in just how different browsers (and versions within brands of) operate. Because... wait for it... this CSS still won't work in several other browsers.
This is why Ruthsarian Menus was developed. I've gone through and tried to iron out every nuance, workaround every bug, to get these menus working in as many browsers as possible. Compare the four-lines of CSS given in the "final" example above with the Ruthsarian Menus stylesheet. Bit of a difference.
I've tried to comment in the CSS on every bug I found and why I chose the CSS I did to work around them. If you're going to use this system, read the stylesheet. There's far more information in that one stylesheet than anywhere else on bugs, hacks, and how the menu system really works.
If you can confirm the menu system works for other browsers or other versions of the browsers listed above please let me know by sending an e-mail to ruthsarian@gmail.com.
* Opera 9.03 is known to exhibit odd behavior in how anchors within list items gain and hold focus. This is a browser bug and not a problem with the CSS.
Here is the stylesheet and here is the javascript and you can view the source of this or the demo page to see how to structure your markup. But that's just tedious, so here's a ZIP file with all the goodies pre-packaged.
Everything provided (except where noted in the javascript file as being copyright someone else) is released into the public domain. This means you can use and abuse the menu system as much as you want without having to get permission or even credit where it came from. You can do anything you want with it.
There is none.
To be brutally honest, this isn't something a person new to CSS should use. You need to be familiar with CSS and HTML and how the two relate. You should have at least some experience developing your own stylesheets before trying to use this. You should know how to link stylesheets and JavaScript to a web page. You should be comfortable editing CSS and HTML you didn't develop. You should be comfortable editing CSS and HTML by hand. Dreamweaver and TopStyle don't count.
If you've got a question or if you've run into a problem you can get in touch with me and I'll see what I can do. Please try and provide lots of details. A simple "this isn't working. why is that?" with a link to or an attachment of a copy of your web page really isn't going to work. Tell me what's broken, how it's broken, what platform you're using, and what you've already tried. Tell me what you think might be the problem, and I'll do my best to look into it. But no guarantees.
Same exact markup is used in each example below with only the class attributes being changed to determine how the menu is rendered.
By default dropdowns appear to the right of their parent list item. But right-aligned menus or left-aligned menus that appear on the right-side of the webpage may result in dropdowns being rendered partially (if not completely) off-screen. So right-side menus are configured to drop to the left.
Horizontal menus are probably the more common structure for the top-level. Left, centered, and right aligned can be done.
The reversed list is a by-product of how the list items are floated. You could work around this by reversing your list, but I much rather keep it.
So how do you use this thing? Well first you need to include two important files into your webpage. One is the stylesheet, rMenu.css. The other is the JavaScript library that contains the functions needed to support older browsers, ruthsarian_utilities.js. There are also some images used to signify the direction a menu item that has a child menu will drop. With these files in place we're ready to go. Where you locate these files on your website doesn't matter as long as you reference them in your HTML correctly. Images should probably go in the same folder as the CSS otherwise you'll need to edit the stylesheet to reflect the correct location, relative to the stylesheet, of the images.
You need to get your list markup in order first. Nothing surprising here. Use
unordered lists (UL
) to craft your menu. Sub-menus should be
new ULs
that are placed inside the LI
element
that contains the menu item the sub-menu should drop from. You don't have to
supply anchors for every menu item but it's strongly suggested you do. These
anchors (A
) should wrap the text (and images if need be) of the
menu item but should not wrap the sub-menu. An example is given below:
<ul class="rMenu-hor rMenu" id="rMenu"
><li class="rMenu-expand"
><a href="">Menu Item</a
> <ul class="rMenu-ver"
><li
><a href="">Menu Item</a
></li
><li
><a href="">Menu Item</a
></li
> </ul
></li
><li
><a href="">Menu Item</a
></li
> </ul>
The markup looks a bit funny. The reason for the funny markup is to keep it (relatively)
readable without allowing whitespace between UL
and LI
elements. If you
have a way of organizing your markup that's different but works for you, go ahead and use it. The key
point here is the lack of whitespace between elements in the markup. But this example
has whitespace! What's that all about? It's for IE/Mac. IE/Mac needs to have
whitespace exist before and only before <UL>
and </UL>
tags. Why? I don't know, it's IE/Mac. It's wierd that and many other ways. This bit of whitespace doesn't
cause any problems with other browsers, so we're okay. Whitespace in other locations will cause
problems, most notably in IE/Win.
You should also notice the use of the class attribute on a couple elements in the example above.
The classes you apply determine the way the menu functions. There are many classes
defined in the stylesheet so be sure to look through it; specifically under
the section of the stylesheet marked Extended Menu Mechanics. Classes are applied only to
the UL
elements with the exception of the rMenu-expand
class which is
only applied to LI
elements to signify that LI
element has a dropdown
menu in it. Some basic classes are:
rMenu-hor
: render the menu as horizontalrMenu-ver
: render the menu as verticalrMenu-hRight
: the menu is horizontal, right-aligned (so dropdown menus should go left of its parent menu item)rMenu-vRight
: the menu is vertical, right-aligned (so dropdown menus should go left of its parent menu item)rMenu-wide
: do not impose a specific width on the menu. let it fill out the width it's containing elementrMenu-noFloat
: do not float the menurMenu-lFloat
: float the menu leftclearfix
: child elements are floated, make the parent element contain them
Note that several of these classes contradict others. You don't want to mix, for example,
rMenu-hor
with rMenu-vRight
. On the contrary, some classes go
together such as rMenu-hor
and rMenu-hRight
. You may notice the lack of
specific left-aligned classes. By default menus are left-aligned so rMenu-hor
does not need a corresponding rMenu-hLeft
class. Clearfix
is sure to create some confusion. Generally you only use this on horizontal menus. Check out the
previous link for information on what Clearfix
is all about.
While the majority of classes are applied to the top-level menu, sub-menus have classes as well. This
is needed in situations where you want a difference between how the top and sub-menus are rendered. In the
example given above you'll notice rMenu-hor
is applied to the top menu. The sub-menu
then takes the rMenu-ver
class. This makes it so the sub-menus display as vertical
menus and not another horizontal menu. I can't think of many situations where you'd want to have horizontal
sub-menus. Really the only place for horizontal menus is the top-level menu. Everything else should be
vertical menus.
Internet Explorer (pre-version 7) does not support the :hover
pseudoclass nor
child selectors. This means only the top-level menu is displayed. With a little JavaScript we can
get the menus working in versions back through 5.0. The origins for this fix come from
Son of Suckerfish. All the
necessary JavaScript is included in the ruthsarian_utilities.js
script included with this package. However you will have to get your hands a little dirty in order
to activate this.
In the example above you'll notice that the top-level UL
has
an id
attribute as well. This id
is passed to a
function when the page loads. This function attaches event handlers to the
LI
elements so that when you mouse over an LI
element it is assigned a new class, sfhover
. We use CSS that
targets this class to make the sub-menus display in old versions of IE.
So you need to have this special function called with the id
of your menu when the page loads. Here's what it'll look like:
<script type="text/javascript" src="ruthsarian_utilities.js"></script>
<script type="text/javascript">
<!--
event_attach( 'onload' , function () {
sfHover( 'rMenu' );
} );
//-->
</script>
The first line loads the external script. The rest is some
embedded JavaScript code that attaches a function that contains
one instruction, sfHover( 'rMenu' );
, to the
onload
event. This means that the sfHover
function will be called when the web page finishes loading. Now
it is possible to have multiple menus in your web page. Each menu
would have a unique id
and you would add a
sfHover
function call for each menu.
The stylesheet contains extensive comments. Read them. You'll gain a better understanding of what's going on and, hopefully, a better idea of how the various classes and markup come together to create the dropdown menu effect. You really should feel comfortable with your understanding of how this all works before you start to really use this menu system.
I've also included a lot of notes about various bugs I encountered while developing this system. If you find something amiss with your implementation try looking through these notes in the stylesheet. You may be doing something that's triggering one of these bugs.
Also look at the markup used on the demo page. This will give you a real-world example of how the menu works, how the classes work together, and what your markup should look like.
One problem with rMenu is that each menu item is a floating block element, even
the horizontal menus. This means we cannot simply text-align:center;
to
create a center-aligned menu. Nor can we convert the horizontal block elements to
inline because the menus then drop relative to the UL element and not the LI element;
the menus would all drop over each other in the same spot.
So is it even possible? See for yourself:
How? Is it magic? Yes, in a way. The short story is this: we push the menu right 50% of the width of the space the menu appears in (width of the column). We then pull the menu back to the left by 50% of the width of the menu itself. This centers the menu. The problem is in getting the menu to use the right value as its base when calculating that 50%. The first 50% is based on the width of the space or column inside which the menu is to be centered. The second 50% is based on the width of the menu itself. So how do we do that?
The UL
element which contains the menu is a non-floating block element
by default for a horizontal menu. Its width is the same as the space the menu will
be centered within (the width of the column). We need to collapse this element so
that it takes on the width of the menu. How do you do that? Float it. Once the
UL
is floating we're ready to start working.
First, we move the menu to the right 50%. This is done with CSS positioning. We set
position:relative;
on the UL
and then set
left:50%
. This pushes the UL
50% to the right. This 50%
is not based on the width of the UL
but on it's parent, which will
have the width of the space the menu is to be centered within.
The second step is to then move all the top level LI
elements in
the menu left 50%. This is done similarly: position:relative; left:-50%;
.
This negative 50% is based on the width of the parent element to the LI
elements, in this case the now collapsed UL
element.
With that the menu is centered.
One thing to note: now that the UL
element is floating we need to wrap it
in a DIV
and apply the clearfix
class to that DIV
otherwise the menu will act as a float and content will start to line up immediately to
the right of the menu. The additional markup is simple and looks like this:
<div class="clearfix rMenu-center">
<ul class="clearfix rMenu-hor rMenu"
.
.
.
> </ul>
</div>
Right now this method does not work on IE/Mac platforms. The menu will simply appear left-aligned. This is not a very big problem, but is something that will continue to be worked on to see if it's possible.