FRANK'S
TUTORIALS

CREATE A DROP-DOWN/FLY-OUT MENU WITH CSS v. 3.0

Making a two-level menu that works in all browsers — and on touchscreens such as iPads and other tablets — can be a daunting task. However, if you follow the few simple steps explained on this page, you'll get it done in no time.

All menus require a standards mode-forcing doctype. The doctype tag must be the very first code line. Any code snippet preceding it, including a comment or an XML tag, will send some or all of the browsers into quirks mode. Only PHP lines and the likes are allowed, because those are processed by the server and removed before releasing the page to the visitor's browser.

I assume that you have a basic knowledge of CSS regarding positioning. If not, you might want to go through How to Position in CSS first. The live examples of the menus are very basic for a reason: then you will understand the code better. I will cover both the horizontal drop-down menu and the vertical fly-out menu.

How to Make a
Drop-down Menu with CSS

There are two options with the horizontal drop-down menu: the width for the main menu items is set, or not, in which case the width depends on the contents of the items. We will first create the basic HTML for both options and later differentiate. The basic HTML is a unique container with in it a two-level unordered list:


    <div id="navDiv">
        <ul>
            <li><a href="#">Hyperlink 1</a></li>
            <li><a href="#">Hyperlink 2</a>
                <ul>
                    <li><a href="#">Hyperlink 2-A</a></li>
                    <li><a href="#">Hyperlink 2-B</a></li>
                    <li><a href="#">Hyperlink 2-C</a></li>
                </ul>
            </li>
            <li><a href="#">Hyperlink 3</a></li>
        </ul>
    </div>

Note that giving the main <ul> the/an ID rather than the container can easily give different results. And it would be wise anyway to make the container unique rather than the list itself, because in HTML5 it is done like that as well, with the new <nav> element. Also note that the second main <li> isn't closed until after its sub-list.

Another point that the two options have in common is that we will line up the main menu items horizontally by means of giving the <li>s a float:left, not a display:inline. This is because with display:inline they are not responsive to width declarations anymore, and the menu items are awkward to join together. Furthermore, display:inline-block is only supported by IE6/7 if it concerns inline elements newWindow (1K).


1. Width for the main menu items set

Here is the live example newWindow (1K) of what we will be making. The first transformation step is to de-style the list and the sub-list, and to line up the main items 1-3 horizontally, leaving the sub-items A-C vertically aligned. The code for that is this:


    #navDiv ul {
        list-style-type: none;
        margin: 0;
        padding: 0;
        }
    #navDiv ul li {
        float: left;
        width: 120px;
        }
    #navDiv ul ul li {
        clear: left;
        }

Although the sub-items A-C would also stay vertically aligned without clear:left, due to the width declaration, you want tensionless layouts and I therefore cleared the float. The result is as is depicted here (image):
menu_hor_1 (1K)

Next, we are going to make the sub-list disappear and appear again upon hovering over the parent <li>:


    #navDiv ul ul {
        display: none;
        }
    #navDiv ul li:hover ul {
        display: block;
        }

This works in all browsers, except IE6. Because a navigation menu is an essential thing, and IE6 is still used in some complicated or expensive IT systems and cannot be upgraded due to compatibility problems, you should put this javascript in the head to make the hover work in that browser version as well.

The Javascript assignes and takes away a(n extra) class, .dynamicClass, for the item with the hover movement. The li:hover declaration may now seem redundant, but we will leave it in place for those users with Javascript off. We will also add that dynamicClass to the CSS (see below).

We have to do a few more things. The first thing is to make sure that upon display of the sub-list, the rest of the page is not pushed down. That is done by adding the following declarations (including the added dynamicClass):


    #navDiv ul li {
        position: relative;
        }
    #navDiv ul li:hover ul,
    #navDiv ul li.dynamicClass ul {
        position: absolute;
        }

Elements with a position:absolute are taken out of the flow, and because you might want to fine-tune the position of the sub-menu, I made it a nested position:absolute. For more on both, see How to Position in CSS. The second thing we need to do more is extend the clickable zone of the hyperlinks to the full width of the <li>s, this way:


    #navDiv a {
        display: block;
        width: 120px;      /* for IE6 */
        }

This will also correct the faulty re-positioning of the sub-list by IE6/7 (next to in stead of under <li> #2) as a result of the nested position:absolute.

The last thing we need to do more is clear the float of the <li>s once more and dress up the menu. See the live example newWindow (1K) for how that is done easily, with the principle being: dress up the <a>s, not the <li>s. The example also contains instructions on how to center the menu.

But wait... there is no hovering on touchscreens like iPads and other tablets... Fortunately, there is a very easy javascript for that as well. Just have a look at this example newWindow (1K). It is based on the same principle as the IE6 javascript: assign an ID to a menu item when it is clicked, take it away again upon the second click, and add the ID to the CSS hover declaration.


2. Variable width for the main menu items

Here is the live example newWindow (1K) of what we will be making this time. We will start with the same div + ul as in the first option, but with a longer text link for second main menu item. The first transformation step is again to de-style the list and the sub-list, and to line up the main items 1-3 horizontally, leaving the sub-items A-C vertically aligned. We will do it without width declaration this time:


    #navDiv ul {
        list-style-type: none;
        margin: 0;
        padding: 0;
        }
    #navDiv ul li {
        float: left;
        }
    #navDiv ul ul li {
        clear: left;
        }

This code should be pretty much self-explanatory again. It gives the following result (image):
menu_hor_2 (1K)

Next, we are going to make the sub-list disappear and appear again upon hovering over the parent <li>:


    #navDiv ul ul {
        display: none;
        }
    #navDiv ul li:hover ul {
        display: block;
        }

Again, this works in all browsers, except IE6. Because a navigation menu is an essential thing, and IE6 is still used in some complicated or expensive IT systems and cannot be upgraded due to compatibility problems, you should put this javascript in the head to make the hover work in that browser version as well.

The Javascript assignes and takes away a(n extra) class, .dynamicClass, for the <li> with the hover movement. The li:hover declaration may now seem redundant, but we will leave it in place for those users with Javascript off. We will also add that dynamicClass to the CSS (see below).

We have to do a few more things. The first thing is to make sure that upon display of the sub-list, the rest of the page is not pushed down. That is done by adding the following declarations (including the dynamicClass):


    #navDiv ul li {
        position: relative;
        }
    #navDiv ul li:hover ul,
    #navDiv ul li.dynamicClass ul {
        position: absolute;
        }

Elements with a position:absolute are taken out of the flow, and because you might want to fine-tune the position of the sub-menu, I made it a nested position:absolute. For more on both, see How to Position in CSS.

The second thing we need to do more is set the width for every sub-list, which is best done by setting it for their <a>s. To be able to do that, we have to give every sub-list a unique ID, and we have to make the <a>s susceptible to width declarations, by declaring display:block for them:


HTML:
    <ul id="item2SubList">

CSS:
    #navDiv a {
        display: block;
        }
    #navDiv #item2SubList a {
        width: 125px; /* varies with the main menu item width */
        }

These declarations also widen the clickable zone of the hyperlinks, to the full <li> width.

The last thing we need to do more is clear the float of the main <li>s again and dress up the menu. See the live example newWindow (1K) for how that is done easily, or this example newWindow (1K) if a sub-menu needs to be wider than its parent menu item. The principle for the dressing up is: dress up the <a>s, not the <li>s. The examples also contain instructions on how to center the menus.

But wait... there is no hovering on touchscreens like iPads and other tablets... Fortunately, there is a very easy javascript for that as well. Just have a look at this example newWindow (1K). It is based on the same principle as the IE6 javascript: assign an ID to a menu item when it is clicked, take it away again upon the second click, and add the ID to the CSS hover declaration.

How to Make a Fly-out Menu with CSS

Here is the live example newWindow (1K) of what we will be making in this sub-tutorial. We are again going to start with, apart from a standards mode-forcing doctype, a unique container with in it a two-level unordered list (I will furthermore pretend that you didn't read the preceding sub-tutorials):


    <div id="navDiv">
        <ul>
            <li><a href="#">Hyperlink 1</a></li>
            <li><a href="#">Hyperlink 2</a>
                <ul>
                    <li><a href="#">Hyperlink 2-A</a></li>
                    <li><a href="#">Hyperlink 2-B</a></li>
                    <li><a href="#">Hyperlink 2-C</a></li>
                </ul>
            </li>
            <li><a href="#">Hyperlink 3</a></li>
        </ul>
    </div>

Note that giving the main <ul> the/an ID rather than the container can easily give different results. And it would be wise anyway to make the container unique rather than the list itself, because in HTML5 it is done like that as well, with the new <nav> element. Also note that <li> #2 isn't closed until after its sub-list.

The first transformation step is to de-style the list and sub-list, and to position the sub-list next to its parent <li>. That goes like this:


    #navDiv ul {
        list-style-type: none;
        margin: 0;
        padding: 0;
        }
    #navDiv ul li {
        width: 100px;
        position: relative;
        }
    #navDiv ul ul {
        position: absolute;
        left: 100px;
        top: 0;
        }

Because the sub-list is a child of the second <li>, it will be 100 pixels wide, too. The principle of relative absolute positioning, or nested position:absolute as it should be called, is explained further on How to Position in CSS. The declaration top:0 is necessary for the newer browsers, because the default positioning is (1 em) below the parent <li>. The result looks like this (image; I added an orange background to make the boundaries clear):
menu_vert_1 (1K)

Next, we are going to make the sub-list disappear and appear again upon hovering over the parent <li>:


    #navDiv ul ul {
        display: none;
        }
    #navDiv ul li:hover ul {
        display: block;
        }

This code snippet works in all browsers, except IE6. Because a navigation menu is an essential thing, and IE6 is still used in some complicated or expensive IT systems and cannot be upgraded due to compatibility problems, you should put this javascript in the head to make the hover work in that browser version as well.

The Javascript assignes and takes away a(n extra) class, .dynamicClass, for the <li> with the hover movement. The li:hover declaration may now seem redundant, but we will leave it in place for those users with Javascript off. We will also add that dynamicClass to the CSS (see below).

We need to do a few more things. The first is to extend the clickable zone of the hyperlinks to the full width of the <li>s, like so (here is the added dynamicClass and the code for the added background color, too):


    #navDiv a {
        display: inline-block;  /* 'block' causes gaps in IE6 */
        width: 100px;           /* for IE6 */
        background: orange;
        }

The last thing we need to do is dress up the menu further. See the live example newWindow (1K) for how that is done easily, with the principle being: dress up the <a>s, not the <li>s.

But wait... there is no hovering on touchscreens like iPads and other tablets... Fortunately, there is a very easy javascript for that as well. Just have a look at this example newWindow (1K). It is based on the same principle as the IE6 javascript: assign an ID to a menu item when it is clicked, take it away again upon the second click, and add the ID to the CSS hover declaration.


Change log
V. 2.0: added How To for horizontal menus with variable widths of the main menu items.
V. 2.1: re-formatted CSS code snippets in the text.
V. 2.2: made the javascript compatible with multiple <li> classes; added centering methods.
V. 3.0: deleted the inline javascript; added instruction to make the menus work on touchscreens as well!


About
I am Frank Conijn, a web designer and author from Amsterdam, the Netherlands. At the moment, I am too busy with other web projects to be making websites for others, but I am available if you want to hire me for problem solving or advice. Just mail me at f.conijn@conijnbotFoolconsultancy.com.

Was this article helpful and do you have any Google account? A Google recommendation of my business, by means of simply clicking this button, is appreciated:

For general comments on and suggestions for this article, mail met at the above address. For free help with minor problems, post your question on one of these web design forums (pop-up).