Using CSS to Generate Expanding Horizontal Navigation Menus in Drupal

08.07.2005

Not to toot my own horn, but everyone loves this site's top navigation menu. I've received more questions about how I did it than I can answer. As a result, I've decided to write a tutorial on it. Before beginning this tutorial, the reader should be somewhat familiar the following:

Also, be sure that you have installed PHP-Template, and a fresh copy of Box_Grey on hand.

Now a quick word of warning: This tutorial, and these concepts are not for the weak-willed and weak-minded. The concepts we're going to cover are inherently frustrating, difficult, and confusing. However, if you work to understand them, and make a commitment to get this right, I promise huge rewards in your overall understanding of CSS. And take it from me, a solid understanding of CSS means more than personal satisfaction, it can also make you a reasonable living.

Phase I: Prepare for Battle

First things first, unzip yourself a new install of box_grey, and rename the "box_grey" folder "madness". Now, let's include a seperate stylesheet for our navigation menu. Using a text editor, open up the file themes/madness/page.tpl.php. On line 8 add some code for the new stylesheet like a so:

<style type="text/css" media="all">@import "themes/madness/menu.css";</style>

Now, we are going to replace what used to be the primary and secondary links with a dynamic full user menu. Go to line 31 and delete everything within the "top-nav" DIV, and replace it with the following code:

<div id="top-nav"><?php $UserMenu = theme_menu_tree(0);print $UserMenu; ?><br class="clearnav" /></div>

By now, you should save your work and check out your site. You should see this:

css

Phase II: Inlines and Floats: Your CSS Sledge Hammer

Obviously, we're not done... Our menus are displaying vertically, and have those lovely box and arrow bullets. My friends, we have no choice but to bring in the CSS.

Remember in the earlier step when I told you to include the file themes/madness/menu.css? Well, we sort of forgot to create that file, so let's go ahead and do it. Hopefully, you don't need me to explain how to create a file called "menu.css" in your themes/madness directory....

Open your new file "menu.css"; it should be a completely blank file. Take a deep breath reader, your understanding of CSS is about change dramatically. Now might also be a good time to brew a pot of coffee. Now its time to go medioeval on this nested list's ass. Write your first rule:

#top-nav li {display:inline;}

So before, our menu was standing up all tall talking trash about our mothers; and we just couldn't let that stand. So we said, "Okay, stupid nested menu, say hello to my little friend!" Or, the more technical version is we told our CSS document that all lines within the top-nav div to display inline. Here is the result:

we totally kicked this menu's ass

Mmmhmmm, the above menu just got served. However, as we will soon find out, our menu still thinks it can talk trash. Click a link to another layer of menus (i.e. click administer). Look at the nerve:

navigation menu still talking trash

At first glance this looks like a nightmare. The weak will give up at this point and simply declare "it cannot be done!". However, we are strong, and we're going to crush this minor inconvience.

The reason that the new layer of menus is splitting its parent menu in half can be found at the markup level. Drupal generates new menus within menu lines like a so:

<div id="#top-nav"><ul><li>EXPANDED MENU ITEM</li>  <ul>    <li>MENU ITEM<li>  </ul></li></ul></div>

Now that we understand how this markup works, we can bust out some CSS-kung fu. Now Grasshopper, add two more CSS rules

#top-nav li {display:inline;} #top-nav ul li ul {float:left; width:100%;}

Pay close attention to how this new CSS rule is written. Where as our first rule #top-nav li applies to all lines within the top-nav div, our new rule is much more specific. In English, it says we only apply this rule only within the #top-nav id, and to unordered lists within lines of an unorder list. Sound confusing? It is, but only because I tried to put it into english. Take a look progression in HTML code for our nested lists a few lines up, follow the elements to "MENU ITEM"; they are:(1)#topnav, (2)ul, (3)li, (4)ul. Look familiar? Perhaps now you'll begin to understand why many geeks wish we spoke code instead of english. In all cases, following the progression like this helps you apply CSS rules like a sharp shooter.

We've told #topnav ul li ul to "float:left". This slides it to the left (obviously), but more importantly, causes the new layer of lists to no longer split parent menus in half, or leave empty space. However, float also by default will causes our list to shrink to its minimum required width. So we've overrided that by specifying "width:100%". Now, look at the magnificent result:

menu beginning to behave

SUPRISE! There is another problem, and this one is particularly scary looking. While the second layer of lists behaves perfectly, the third layer corrupts our entire layout, sliding to the right by no less than 1000px!

what happened

Frankly reader, we don't know, and we don't care. There is any easy solution. Add your third CSS rule and finish today's foe for good:

#top-nav li {display:inline;} #top-nav ul li ul {float:left;width:100%;}.clearnav {clear:both;}

Shazam, one shot, one kill. ".clearnav {clear:both}" was the missing link. We now have menus which will expand horizontally, without problems, into infinity. If you must know "clear" typically clears out any odd behaviors that are caused by floats. Observe our unstyled menu's beauty:

quarter of the way there

The good news is that by this point we're about a quarter of the way there. Er, wait that's the bad news, sorry. Today, we've merely created the foundation to our menus. However, I want to personally congradulate all three of you that have managed to get this far. In our next tutorial I will teach you how to style the individual levels of the menus. Specifically, we'll cover how to apply different styles for menus that are expanded, collapsed, active, or dead ends. In our third tutorial we will cover advanced styling such as shades, rounded tabs, or entirely different displays (however, I myself am still figuring out how to do that...)

As always, please let me know if you have any problems with this tutorial. I will not only be happy to help you, but most likely others are experiencing the same problems as your are. So asking for help not only helps you, but it helps anyone else who is attempting to learn these concepts.

Comments

this tutorial sucks !

it doesnt show anything. the author clearly doesnt know what he is talking about.

Can't seem to make this work

Thanks so much for this tutorial, but I've followed your instructions with box_grey and my menu items appear one on top of another. I did notice that the div in page.tpl.php in the original file did not start on line 31, but I replaced its contents according to the tutorial. Any suggestions?

Thanks!

So far so good, pretty much

I finished part one, and it looks great in Firefox and Safari, but in IE 5.2 on the Mac, the second layer is still splitting the top menu. Since MS doesn't support IE on the Mac anymore, it's minor to me except that we have many users who continue to use it (I have trouble imagining how). So, I'll keep working through the next steps and hope when I can test it on a PC that it will be okay there. Thanks for a really well-written tutorial.

Problems implementing this

Hi Nick,

Excellent tutorials!

I am using the friendselectric theme and came unstuck when simply adding the inline code to the top-nav. They just don't go inline!

When i look at source, i notice there are span classes around the list items (eg)

As you can see this is no way as simple as you example above. How can i modify the new menu created just for this, so that all that extra styling is ditched? ..and then hopefully i will have an inline menu :)

Are you generating a plain

Are you generating a plain menu via the fuction theme_menu_tree($mid)? There should be no spans in the menu... if there are, its the result of extra scripting, or a weird rule in the template.php file. Itmight have something to do with the friends eclectic theme... I never use it myself, as it has a lot of bulk, and has the potential to get crazy when you attempt to style it.

Some results

Ok, i have removed the follwoing from the template file:

function phptemplate_wrap_links($link, $n) {
  $classes = array("lw1", "lw2");
  $before = $after = "";
  foreach ($classes as $c) {
    $before .= '<span class="'. $c .'">';
    $after .= '</span>';
  }
  $link = preg_replace('!<a[^>]*>!i', '\0'. $before, $link);
  $link = preg_replace('!</a[^>]*>!i', $after . '\0', $link);
  return $link;
}

function phptemplate_menu_item_link($item, $link_item) {
  /* Wrapper span */
  return l('<span class="lw1">'. check_plain($item['title']) .'</span>', $link_item['path'], array_key_exists('description', $item) ? array('title' => $items['description']) : array(), NULL, NULL, FALSE, TRUE);
}

The results is that the span classes are removed. However, again looking at the html source, i still have expanded and leaf ids:

     

<div id="top-nav">
<ul>
<li class="expanded"><a href="" title="" class="active">Top category 1</a>
<ul>
<li class="leaf"><a href="" title="">2nd level 1</a></li>
<li class="leaf"><a href="" title="">2nd level 2</a></li>
<li class="leaf"><a href="" title="">2nd level 3</a></li>
</ul>
</li>
<li class="leaf"><a href="" title="">Top category 2</a></li>

</ul>
<br class="clearnav" />
</div>

Now i have, as you have suggested, set #top-nav in my style.css. However the following code that is needed to style the menus in the sidebar also affects the top-nav lists as well:

.item-list ul li, li.expanded, li.collapsed, li.leaf {
  list-style-type: none;
  list-style-image: none;
  margin: 0;
  padding: 0px;}
 
.item-list ul li a, li.expanded a, li.collapsed a, li.leaf a {
  margin: 0;
  padding: 3px 1px 3px 5px;
  display: block;}

  li a.active {font-weight: bold;padding-left:15px; color: #36456c;background: url(indent1.png) no-repeat 4px 5px;}
 
.item-list ul li a:hover, li.expanded a:hover, li.collapsed a:hover, li.leaf a:hover{
  background-color: #fff;
  color: #486e98;
  padding: 2px 0px 2px 4px;
  border: 1px solid #ccc;
  font-weight: bold;
  background-position: 3px 4px;}

li a.active:hover{
  background-color: #e2ecf9;
  color: #36456c;
  padding: 2px 0px 2px 14px;
  border: 1px solid #e2ecf9;
  background-position: 3px 4px;}

Please bare with me, as i am a complete css newbie, but how can i set the code above to JUST style the the menus in the sidebar?

I tried things like adding #sidebar to each of the styles above, but everytime it affected everything, not just the sidebar menus....

Tell me about it!

I have had nothing but problems restyling FE theme! This is the code found in the template.php file: function phptemplate_menu_item_link($item, $link_item) { /* Wrapper span */ return l(''. check_plain($item['title']) .'', $link_item['path'], array_key_exists('description', $item) ? array('title' => $items['description']) : array(), NULL, NULL, FALSE, TRUE); } How can i bypass this for just this menu? (btw Apologies for not placing my last comment code inside code tags!!)

Short answer

ASAIK just remove the code i put above. The only major side effect is that the edit/view/track tabs when logged in collapse as their styling disappears. Would be nice if they were sorted but not in public view, so not totally bothered. :)

Anyway, by emoving that code and setting the ul/li for the side menus to #sidebar-right (or left whichever you are using) i managed to set up a great looking top nav menu. Thanks Nick!

Your menu tutorial

Hi I'm playing around with Drupal to see if I can remodule my homepage to fit in to Drupal. Found your tutorial here extremely helpful. But I wonder. How do I get rid of the menutitle? When Drupal creates my menu the title , for example "Menu 1" becomes top level. I want it to be like in your example, where the different topics are on the toplevel of the list. How did you do that?

xtemplate.xtmpl

Thank you Nick, Menu works great in page.tpl.php but site was committed to xtemplate.xtmpl prior to my involvement so wondering if you can direct me to a fix that will allow call for menu from xtemplate.xtmpl file?

Lucid explanation from a

Lucid explanation from a GEEK. The step by step tutorial is highly conversational with cool snapshots, well done. Looking for more articles on drupal + css. You might wanna look at sliding doors implementation http://capmex.biz (drupal specific) and rounded corners at http://www.stephenhendry.net/ (pure css) I can hardly wait for your next article on menus... Your site layout (think its fluid) is also good.

Gunny, I'm already ahead of

Gunny, I'm already ahead of you, I've written part deux to this article, and will probably churn out part three by the end of the day. Thanks for the compliment, btw. I feel pretty strongly about making this knowledge accessable, and the learning process tolerable, so you told me exactly what I wanted to hear! Thanks for the encouragement. As for sliding doors, and rounded tabs, that is something I'm actively experimenting with... I'm not quite sure its possible yet without customizing the drupal menu module. However, at the very least, we'll get to discussing how to spruce the menus up. Not to mention, big, scary, hard core gradients and curves are seeming to go out of style. Which is not bad -- as they are really hard to make work all browsers.

no need for customizing the menu.module

...there is no need for customizing the menu.module check out a rounded corner example(pure css) for primary links at http://www.langmi.de (rounded corner .gifs are used too but for the overall layout)

Thanks for the tip Michael.

Thanks for the tip Michael. However, I'm afraid this technique won't work for us. I took a look at the source code for the tabs, take a look:

<ul><li><a href="/home" class="menu"><b class="snazzy"><span class="boxcontent active">Home</span><b class="b4 darkblue"></b><b class="b3 darkblue"></b><b class="b2 darkblue"></b><b class="b1"></b></b></a></li>...</ul>

While this a pure CSS solution, it is far too bulky to be a good choice (in my opinion) for a large global expanding menu that contains hundreds of links. THis is for two reasons, one the extra markup (i.e. b class) can get really messy when required to display across more than one level, two the extra markup, in our case, will likely hurt a site's search engine ratings (google hates it when lots of links are marked up like this). In addition, this solution would require customization, refer to the source code for drupal's menu items in my latest article:... as you can see, there are some pretty profound differences in the source code.

However, on a less oppositional note, this is a very odd solution, and one that I've never seen before. It works perfectly on both browsers, and I'll explore it further. My first impression was that it was unnecessarily complex -- but then again, as I keep finding out, I am a cocky idiot. Thank you, again, for pointing me to this.

Also, quick note, my spam filters have gone FUBAR thanks to an overnight attack by fringe pornographery spammers (oh the phrases they left...), so at the moment, legit comments are being unpublished. I'll be checking up on my unpublished comments through out the days, so fear not, you will have your say... it might jst not be immediate.

a bit more info

see http://www.stunicholls.myby.co.uk/menus/snazzymenu2.html for more info on this link design (and dont miss the other examples) my starting point on the drupal side was http://drupal.org/node/18451 atm i play around with the drupal theme system, trying to push it to the extreme and make some "proof of concepts" so i totally agree, the html code is just too much and for big link listings i prefer other solutions :-) i like your tutorials and maybe its possible to merge them with some AJAX, i think it would perfectly fit for those big link listings

No kidding about avoiding

No kidding about avoiding the bulk. This blog's current design is actually far simpler than my past 4 blog designs, however -- I think its the best and I suppose the lesson I am learning is that solid colors and boxes often look better than curves and shades, in addition to allowing pages to load much faster. But in the end its neither the "look" or the efficency that really has steered me clear of the bulky solutions, but rather the fact that it seems to destroy the point of drupal. Horizontal , curved tabs with shades can be easily done on static pages, because the pages are static, and do not change. But with drupal, the whole advantage is being able to evolve the structure of the site, and allow it to naturally adapt to your needs. As soon I start hacking away to make some sort pretty css technique work, I almost always loose the flexiablity that makes drupal so powerful. So I suppose that is the chief challange and promise of this technique -- is to do advanced designs that don't risk making our site rigid, and brittle. I'm not the first to do tabs of course, but the chief thing that I think makes this experiment special is that these tabs not only dynamically update, but they work no matter how simple or complex the site's organization might become, and the technique can be applied to everyt drupal site that hasn't screwed with its menu modules page serving (not to mention once the CSS is layed, they'll work forever, and if you want to change the look colors, shapes, backgrounds,ect.. all you have to do is change the CSS document, not template, module, or Information archetecture changes will ever be required). Thanks for the link to http://www.stunicholls.myby.co.uk/menus/snazzymenu2.html That's actually very similar to the experiments I'm doing right now... the idea is to create the shapes of the tabs by placing a curved background inbetween inline line elements as opposed to actually creating expanding boxes on all sides (such as with sliding doors). The problem I keep having is that I hate the idea of having to use images... it gets cumbersome, and it limits the number of ways a novice could implement a technique. I've discovered some strange behaviors that are allowing me to create curves, though, without the use of images. We'll see how that goes! BTW, what a scary starting point with drupal you had! More later, I'm currently working on a theme for a client. BTW, too bad google's translation of German is so horrid! It looks like you have some very interesting things to say on your blog.

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • You may post code using <code>...</code> (generic) or <?php ... ?> (highlighted PHP) tags.
  • Lines and paragraphs break automatically.
  • Web page addresses and e-mail addresses turn into links automatically.

More information about formatting options