[59315 views]

[]

CSS Navigation

Abstract

Nowadays I code all navigation menus as simple HTML lists, because it's semantically correct and easy to work with.

In this article I will show, how such a list can be nicely laid out as a navigation menu by applying standard CSS. Furthermore I will show client-side JavaScript code that analyzes the DOM-tree of the menu and modifies it to include more information that can be used to further style the menu.

A navigation is a nested list

Take the following example:


<ul>
  <li><a href="1.php">Things</a></li>
  <li><a href="2.php">Animals</a>
      <ul>
        <li><a href="2-1.php">Cani</a>
            <ul>
              <li><a href="2-1-1.php">Domestic dogs</a></li>
              <li><a href="2-1-2.php">Wolves</a></li>
            </ul>
        </li>
        <li><a href="2-2.php">Felidae</a>
            <ul>
              <li><a href="2-2-1.php">Domestic cats</a></li>
              <li><a href="2-2-2.php">Wild cats</a></li>
            </ul>
        </li>
      </ul>
  </li>
  <li><a href="3.php">Humans</a></li>
</ul>

which without special styling renders as

Applying basic style

By applying some basic CSS rules such a list can be rendered a lot nicer than that. We clearly separate menu entries with lines. A visual feedback is given, when the user points the mouse on an item. The whole coloured area is clickable, not just the text.

Noteworthy:

The CSS used to style this menu was


div#s1 {
  width: 200px;          /* menu width */
}

div#s1 ul {
  background-color: #036;
  list-style-type: none; /* get rid of the bullets */
  padding:0;             /* no padding */
  margin:0;              /* no margin for IE either */
}

div#s1 ul li {
  margin: 0;
  padding: 0;
  background-color: #036;
  display:block;
  border-top: 1px solid white; /* lines */
}

div#s1 ul li a {
  display: block;         /* lines extend to right, make area clickable */
  color: white;
  background-color: #036;
  padding: 3px 3px 3px 23px;
  margin:0;
  text-decoration: none;
  height:15px;           /* hint for IE, alternatively remove whitespace from HTML */ 
}

div#s1 ul ul li a {
  margin-left: 20px;     /* indent level 1 */
}

div#s1 ul ul ul li a {
  margin-left: 40px;     /* indent level 2 */
}

div#s1 ul li a:hover {
  color: red;
  background-color: #06C; /* rollover effect */
}

Trees of nodes and leaves

Before I go on, I want to call things by their proper names. A menu hierarchy is a tree data structure. A tree consists of nodes. Every node has a parent node and may have child nodes. If a node has no child nodes, we call it a leaf.

Tree semantics

Now the menu looks nice. But it is not very usable yet. To improve usablibity we want the following:

In order to write CSS rules for the above features, we need more information about the LI elements. Every LI element can have the following properties:

Note that a closed node can never be at active (at the same time). Because when we activate a node, we automatically want it to open.

Those semantic properties should be assigned as CSS class attributes to the LI elements. Let's define the following class names.

Class Meaning
active The menu entry corresponds to the current page
leaf The menu entry is a leaf in the navigation tree
open The menu entry is a open node whose submenus are shown
closed The menu entry is a closed node whose submenu are not shown

Analyzing the tree

The properties defined above, can be assigned automatically. We don't need to add class attributes manually. For this job we employ JavaScript code.

The Code defines a function menu_main(id). It must be passed the id of the element that contains the navigation. In our example, we just pass in the ID of the enclosing div tag. It's a good idea, to call this function through the onLoad event handler of the body tag. The HTML basically looks like so:

<body onLoad="menu_main('navigation')">
  <div id="navigation">
    <ul>
      ...
    </ul>
  </div>
</body>

The client-side JavaScript can of course easily be replaced by server-side logic that prints out the CSS classes on the LI elements.

Highlighting and collapsing

With all this information now available as individual classes on the LI elements, we can now write a few simple CSS rules to highlight the current active node, and collapse everything but the active branch of the tree.


div#s1 li ul, div#s1 li.open li.closed ul {
  display: none;         /* collapse */
}

div#s1 li.open ul {
  display: block;        /* expand */
}
 
div#s1 li.active a {
  color: red;            /* highlight text */
}
 
div#s1 li.active li a {
  color: white;          /* fix lower levels */
}

Bullets

We need three bullets: a leaf, an open node, a closed node. For each of them we want a variant when the node is active. We will then use them as background images on the A element. If we make the active and inactive images separate image files we need to write CSS rules that match for two classes at the same time. Actually this is possible with CSS2 like so for instance: a.leaf.active. The problem with this approach however is, that we would need 3 * 2 = 6 individual rules and (worse) IE does not support this sort of selectors.

Fortunately there is a more elegant solution. We draw the bullets for the active and the inactive state onto the same image, but in different positions. So here are the three bullet images on a blue background. The bullet's background is transparent.:

Leaf: bullet Closed: bullet Open: bullet

As you can see, we have drawn the red version below the white version onto the same image. We can use the nifty background-position CSS property to shift the background image to the right position. This way we have accomplished orthogonality for all properties. Our final piece of CSS looks like this:


div#s1 ul li.open a {
  background-image: url(bullet_open.gif);
  background-repeat: no-repeat;
}

div#s1 ul li.closed a {
  background-image: url(bullet_closed.gif);
  background-repeat: no-repeat;
}

div#s1 ul li.leaf a {
  background-image: url(bullet_leaf.gif);
  background-repeat: no-repeat;
}

div#s1 li.active a {
  background-position: 0px -20px;
  color: red;            /* highlight text */
}
 
div#s1 li.active li a {
  background-position: 0px 0px;
  color: white;          /* fix lower levels */
}

When the menu entries wrap on more than one line, just separate the two images by some more transparent space. A few hundred pixels should be enough for most people.

Putting it all together

Now our CSS navigation is ready. We can include the same static HTML file with our navigation list into every page. On every page we just load the JavaScript code and call it. Automagically the menu figures out the menu structure and which one is the current entry, marks them with CSS classes and our CSS is applied by the browser's rendering engine.

Check out the ready site now.

Conclusion

I have shown a way to implement a navigation, that is completely based on a simple HTML list. I have shown how a client-side script can analyze the navigation and annotate it with CSS classes. Finally I have shown how such a list can be orthogonally styled in a flexible fashion. The CSS in this article can be used as a template whenever a vertical hierarchical navigation is needed. Tested on Mozilla 1.7.3, Firefox 1.5, IE 6, Safari and Konqueror.

Downloads

Followup

In a followup article I show how to use AJAX and DOM to generate the navigation completely on the client-side without having to include the navigation into every single page.