Ruthsarian Menus

Demo : Download : Updated 2007.06.08

Why?

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.

How?

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?

Hacks

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!

Really So Simple?

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.

Confirmed Working

  • Internet Explorer for Windows 5.0, 5.5, 6, 7, 8
  • Opera 7.5, 8.5, 9*
  • FireFox 1.5, 2.0, 3.0
  • Netscape 7.0
  • Internet Explorer for Macintosh 5.0, 5.2
  • Safari 1.3.2
  • iCab 3.03

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.

Source

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.

License

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.

Support

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.

Working Examples

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.

How To

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.

Markup

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.

Classes

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 horizontal
  • rMenu-ver: render the menu as vertical
  • rMenu-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 element
  • rMenu-noFloat: do not float the menu
  • rMenu-lFloat: float the menu left
  • clearfix: 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.

JavaScript & IE

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.

Check The Stylesheet

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.

Centered Menu

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.