Posted by: Donald Howard | June 2, 2012

CSS Horizontal MultiLevel Menus

A fixed horizontal multilevel menu is probably one of the more difficult projects for the occasional CSS user. There are a lot of options on how one puts the logic in place which can be confusing when looking to the Internet for assistance. No two developers seem to do it the same way. That is okay because there are only a couple of keys that you need to be aware of, the rest is individual creativity but not overly complex.

This session is about how to create a purely CSS fixed horizontal multilevel menu. There is NO javascript used in this session. We will focus on just those properties and values that are important to creating a horizontal multilevel menu. I will not be defining all of the CSS used in this session, if you have not already you should go through my CSS Essentials Session before continuing.

Okay lets get the keys out in the open right away,

1. All ULs other than the Main Menu (UL id=’nav’) must be positioned absolute. These will be the submenus in our example,

2. All ULs (submenus) should be set to the CSS property ‘display: none;’, until the user hovers the cursor over a menu item (LI) that contains a submenu. Then display should be set to block.

3. The ULs (submenu) should be offset to the right (or left) of their parent to avoid overlap. This presents a potential problem with document space that can be handled using a little placement logic.

4. The list items (LI) for the Main Menu (UL id=’nav’) should be floated left to appear horizontally. Whereas the submenu list items will display in their default, block element arrangement (vertical). Also we will set their position property as relative so that we can use them as a reference for the positioning of subsequent submenus.

Here is the HTML code for MultiLevel Menu:

<div>
     <ul id=”nav” class=”one”>
          <li><a href=”#” title=”Menu11″>Menu11</a></li>
          <li><a href=”#” title=”Menu12″>Menu12</a>
               <ul class=”two”>
                    <li><a href=”#”>Menu121</a></li>
                    <li><a href=”#”>Menu122</a></li>
                    <li><a href=”#”>Menu123</a>
                         <ul class=”three”>
                              <li><a href=”#”>Menu1231</a></li>
                              <li><a href=”#”>Menu1232</a></li>
                              <li><a href=”#”>Menu1233</a></li>
                         <!–ul>
                    </li>
               <!–ul>
          </li>
          <li><a href=”#” title=”Menu13″>Menu13</a>
               <ul class=”two”>
                    <li><a href=”#”>Menu131</a></li>
                    <li><a href=”#”>Menu132</a>
                         <ul class=”three”>
                              <li><a href=”#”>Menu1321</a></li>
                              <li><a href=”#”>Menu1322</a></li>
                              <li><a href=”#”>Menu1323</a></li>
                         <!–ul>
                    </li>
                    <li><a href=”#”>Menu133</a></li>
               <!–ul>
          </li>
          <li><a href=”#” title=”Menu14″>Menu14</a>
               <ul class=”two”>
                    <li><a href=”#”>Menu141</a></li>
                    <li><a href=”#”>Menu142</a>
                         <ul class=”three”>
                              <li><a href=”#”>Menu1421</a></li>
                              <li><a href=”#”>Menu1422</a>
                                   <ul class=”four”>
                                        <li><a href=”#”>Menu14221</a></li>
                                        <li><a href=”#”>Menu14222</a></li>
                                        <li><a href=”#”>Menu14223</a></li>
                                   </ul>
                              </li>
                              <li><a href=”#”>Menu1423</a></li>
                         </ul>
                    </li>
                    <li><a href=”#”>Menu143</a></li>
               </ul>
          </li>
     </ul>
</div>
 

Note that we will isolate the style rules for the HTML above by prefacing all style rules with ‘#nav’, meaning they only apply to this specific menu! Also note that #nav is the ID attribute of the outer most UL element in our menu (the Main Menu). Thus when our selector begins with #nav it is synonymous with our outer most UL element.

Lets take a quick look at what this HTML would look like with NO CSS other than the Browser’s default CSS.

Note that the menus reflect the nesting of the HTML and the ULs are indented to reflect that nesting. Also the list items (LI) are preceded by bullets and the anchors (A) are emboldened and underlined. This is all basic browser styling! Vertical is the default behavior.

Lets take a quick look at our HTML with the following minimum CSS. Note the resulting horizontal multilevel menu is NOT functional. At this point I just want a start point that we can build upon to get the solution we want.

#nav {
list-style-type: none;
}
#nav ul {
list-style-type: none;
display: none;
}
#nav > li {
float: left;
}
#nav li {
background-color: #333;
}
#nav a {
text-decoration: none;
padding-top: 10px;
padding-bottom: 10px;
padding-left: 10px;
padding-right: 10px;
display: block;
color: #fff;
}
#nav a:hover {
background-color: #fff;
color: #333;
}
#nav li:hover > ul {
display: block;
}
A

A

The submenus (UL) are not displayed unless the mouse is hovered over a menu item (LI). This style rule makes sure that the submenus are not displayed – #nav ul {list-style-type: none; display: none;} and have no bullets when they are.

This style rule makes the submenu visible when the mouse is hovered over an menu item (LI) that contains a submenu (UL) – #nav li:hover > ul {display: block;}. This one of the more involved selectors, it says that the browser should display any UL element that is a direct descendent of an LI element over which the mouse pointer is hovering, if it is within the #nav container (our outer most UL with an ID attribute = nav).

We have converted the vertical menu into a horizontal menu using the #nav > li {float: left}. Note that we only want the main menu to be horizontal so we limit the selector to include only the LI elements that are direct descendents of the outer most UL (#nav / main menu).

The other style rules just deal with the cosmetics and are self explanatory.

I said that with this CSS the menu is not functional and that is true. Looking at the expanded Menu14 above you can see Menu1423 and Menu143 are not reachable. They look reachable, but as you move the cursor down to them the submenu above closes and the menu contracts resulting in a mouse pointer hovering over an empty space. There are a lot of other cosmetic problems as well.

Note how the submenu automatically appears below the menu item (LI) that the mouse is hovered over all the way down (all levels). Each submenu indented to the right.

Lets get rid of that indentation by adding the property – padding-left: 0px; to the #nav ul style rule. We are effectively getting rid of the Browser CSS rule. Our complete style rule now looks like this – #nav ul {list-style-type: none; display: none; padding-left: 0px;}.

Now expanding the fourth menu looks like this:

Doesn’t look like much of an improvement at this point but it is an important one to our design. Remember as we said earlier only the main menu items will be laid out horizontally the submenus will be vertical. Getting rid of the indentation will give us better control of placement of the submenu.

To enable placement we need to apply the positioning property to select elements. Note that both keys 1 and 4 call for the application of positioning. Before we proceed lets review what the CSS Specification says with regard to positioning.

If an element does not state a position property then the default positioning is applied. Default is static, which means that elements are rendered in the order they appear in the document flow. The left, top, right and bottom style properties have NO effect on static elements. That means their placement can not be changed!

Relative positioning is similar to static positioning, but the placement of relative positioned elements can be modified with the left, top, right and bottom style properties.

The placement of an absolute positioned element is relative to its first positioned (relative, fixed or absolute) ancestor element. I will refer to this as a reference element in this session. The left, top, right and bottom style properties affect absolute positioned elements similarly to relative positioned elements.

Using a snippet from the HTML above:

     <li><a href=”#”>Menu1422</a>
          <ul class=”four”>
               <li><a href=”#”>Menu14221</a></li>
               <li><a href=”#”>Menu14222</a></li>
               <li><a href=”#”>Menu14223</a></li>
          </ul>
     </li>
 

We can see that the LI element tag contains a UL element (submenu). Using the positioning logic above if we make the LI positioning relative and the UL positioning absolute we can position the nested UL element with respect to its parent LI element.

First we need to modify the CSS to show the positioning of elements:

#nav li {
background-color: #333;
position: relative;
}

This style rule says that all LI elements are positioned relative. We don’t actually want to change their position but we do want to be able to use them as a reference (remember – an absolute positioned element is relative to its first positioned (relative, fixed or absolute) ancestor element). In the next style rule we set the positioning for all ULs after the Main Menu (outer most UL) to absolute.

#nav ul {
position: absolute;
list-style-type: none;
display: none;
padding-left: 0px;
}

Now that we have the position property set we can use the Top, Bottom, Right, and Left properties to affect the position of the absolute (UL) element in reference to its parent, the relative (LI) element. We actually only need two, left and top.

#nav ul li ul {
left: 100%;
top: 0%;
}

The selector for this style rule is saying that these properties apply to any UL elements that are nested in an LI element which in turn is nested in a UL element that is nested in the element identified by the id attribute equal to #nav (our outer most UL). What this does is to eliminate the Main Menu (outer most UL) from the selection. All menu items (LI) below the Main Menu with a nested UL element will have the submenu position relative to itself. With the UL element appearing to the right of the LI reference element and at the same top as the LI element.

Now the expanded fourth menu looks like this:

Notice how the right edge to the reference LI element (Menu1422) is touching the edge of the submenu starting with menu item (Menu 14221) and also how the tops of the reference LI and the submenu align. That is all a result of the properties ‘right’ and ‘top’ but could not have been done without the positioning of the appropriate LI (relative) and UL (absolute) elements.

That’s more like what we are looking for. We noted in the Keys that screen space may be a problem with any horizontal menu. If the screen width can not accommodate all of the submenus as they expand to the right and down, the Browser will add a vertical and horizontal scroll as necessary. Unfortunately under some circumstances this will be inadequate and the user will not be able to access some menu options without reducing the magnification. There are a few options for dealing with this; 1) place the main menu options with the fewest submenus at the edges, 2) reverse the positioning of the submenus at both extremes of the menu so that the submenus move in the opposite direction, 3) change the size of the blocks that represent the anchor elements (font, padding, etc…).

For example we might change the property left: 100% to right:100% above to redirect the expansion of the fourth menu.

There is another important issue associated with any multilevel menu. You have to make sure that if the submenus appear over any other element with a hover pseudo class that the z-level for the menu is higher than the other element(s). Otherwise if a submenu appears over the top of another element it may lose focus when the mouse moves over the other element.

The z-level property must be applied to a positioned element. The easiest place for us to implement this property is probably in the #nav li style rule. Adding the new property to the style rule we have:

#nav li {
background-color: #333;
position: relative;
z-index: 1;
}

Note we could have also added this to the #nav ul style rule. The only difference is that for this rule the main menu menu items (LIs) are not covered. Not a problem with our design. Some might prefer to get the maximum benefit by adding it to the #nav style rule. In this way the whole menu is covered. If you choose to do this be sure to add both the z-level and a position property.

Okay lets add a little color to make the changes standout a little. You may have noticed that the UL elements in our HTML menu have been assigned ‘class’ attributes. The classes stand for the different nested levels of our menu. Our Main Menu (outer most UL) does not have a class attribute but each of the submenus do. The three sublevels are assigned attribute classes 2, 3, and 4. We can assign a different color for each sublevel like this:

#nav .two {background-color: red;}
#nav .three {background-color: navy;}
#nav .four {background-color: purple;}

I also removed the background-color from the #nav li style rule and moved the same to the #nav > li style rule. In this way only the Main Menu menu items will have a black background. The result looks like this:

Notice that I set the background-color at the UL element. If you read CSS Essentials you know that ‘background-color’ is a property that is NOT inherited but that appears to be what is happening. I mean the ‘A’ element shows the background-color we just assigned to the UL element. True, but this is not inheritance it is the result of the fact that the default background-color for most elements is transparent. This is true for both LI and A elements. So what you are seeing is the UL background-color showing through. If you assign a background-color to either the LI or A element you will lose the UL background-color.

These are the fundamentals that I think you should understand about horizontal multilevel menus. Here is the final CSS for this session:

#nav {
list-style-type: none;
}
#nav ul {
position: absolute;
list-style-type: none;
display: none;
padding-left: 0px;
}
#nav > li {
float: left;
background-color: #333;
}
#nav ul li ul {
left: 100%;
top: 0%;
}
#nav li {
position: relative;
z-index: 1;
}
#nav .two {
background-color: red;
}
#nav .three {
background-color: navy;
}
#nav .four {
background-color: purple;
}
#nav a {
text-decoration: none;
padding-top: 10px;
padding-bottom: 10px;
padding-left: 10px;
padding-right: 10px;
display: block;
color: #fff;
}
#nav a:hover {
background-color: #fff;
color: #333;
}
#nav li:hover > ul {
display: block;
}

The final CSS was validated at W3C and passed both specification 2.1 and 3 with one warning. It did not like that I had the same background-color and color property values in a:hover{} and a{} style rules. Flipping between black and white, not sure what the concern is.

I also reviewed the results in all major browsers and found the behavior and appearance to be the same.

Enjoy.

Don


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Categories

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: