Create a multilevel Dropdown menu with CSS and improve it via jQuery

Some of you might have noticed, I have a partiality for sleek menus. As I recently had to create a multi level dropdown menu for one of my customers, I wanted to improve it with a little bit of jQuery, but couldn’t find a script that accomplished what I needed.

So I decided to build this menu from scratch and share my thoughts as well as the code with you.

So before we start: this is what we are going to build


The first part of this tutorial is dedicated to the task of building a working CSS-only dropdown menu (also known as suckerfish menu), the second part will show you how you can pimp the whole thing with a few lines of jQuery.

The CSS-only menu is cross browser tested and from what I can tell works with all browsers except for IE6.
The Internet Explorer needs the addition of our jQuery function to work properly.

To create a CSS-only dropdown menu that works without Javascript (even in IE6), you need tons of extra markup and CSS, if you really need this for any reason check out Stu Nicholls CSSplay, he addresses this problem with heavy (ab)use of conditional comments =)

Now that you have a little bit of background information, lets create our own menu:

First of all we need the XHTML Structure of our soon-to-be terrific menu. We will use a nested list for this purpose, top level list id is “nav”:

<ul id="nav">
    <li><a href="#">1 HTML</a></li>
    <li><a href="#">2 CSS</a></li>
    <li><a href="#">3 Javascript</a>
        <ul>
            <li><a href="#">3.1 jQuery</a>
                <ul>
                    <li><a href="#">3.1.1 Download</a></li>
                    <li><a href="#">3.1.2 Tutorial</a></li>
                </ul>
            </li>
            <li><a href="#">3.2 Mootools</a></li>
            <li><a href="#">3.3 Prototype</a></li>
        </ul>
    </li>
</ul>

Thats it for the HTML part; without CSS styling our menu looks like this: Step 1

Now for the stylesheet part:

#nav, #nav ul{
     margin:0;
     padding:0;
     list-style-type:none;
     list-style-position:outside;
     position:relative;
     line-height:1.5em;
 }

This removes the indents browsers tend to make, as well as the bullets from #nav and all its child-ul elements. The “position:relative” is needed since we will arrange some of the contained elements with position:relative and absolute. This is necessary since relative and absolute positioned elements are positioned according to their containing blocks with a position attribute, other then static.

Line-height defines the height of each list item. You could set the height attribute for your list-items to define their height, but line-height will center the link text vertically without the need to play with margins and paddings.

 #nav a:link, #nav a:active, #nav a:visited{
    display:block;
    padding:0px 5px;
    border:1px solid #333;
    color:#fff;
    text-decoration:none;
    background-color:#333;
 }

#nav a:hover{
    background-color:#fff;
    color:#333;
}

This one is pretty straight forward:
it will style each hyper link in our menu a little bit. At this time the menu looks like this: Step 2

Now lets add some more styles:

#nav li{
    float:left;
    position:relative;
}

This will align our list elements horizontally.

#nav ul {
    position:absolute;
    width:12em;
    top:1.5em;
    display:none;
}

This will position the nested Lists right beyond the main menu and give them a width of 12em. The width attribute is needed so that the list items within display vertically again. The Top attribute should have the same value as the line-height attribute we defined for #nav.

#nav li ul a{
    width:12em;
    float:left;
}

This will set the width of the hyper links to 12 em (which in combination with the width of the UL set above results in a horizontally displayed sub menu, despite of the ongoing float:left)

#nav ul ul{
	top:auto;
	}

#nav li ul ul {
    left:12em;
    margin:0px 0 0 10px;
    }

#nav li:hover ul ul, #nav li:hover ul ul ul, #nav li:hover ul ul ul ul{
    display:none;
    }
#nav li:hover ul, #nav li li:hover ul, #nav li li li:hover ul, #nav li li li li:hover ul{
    display:block;
    }

#nav ul ul and #nav li ul ul define where we display the sub menus.

The hover states define which items we want to show when hovering over an item (only the next sub level, not all of them)

After applying these styles we got this menu: Step3

If you are working with a browser other than IE6 you should have a basic dropdown menu now.

Gogo jQuery!

So lets spice up the menu a little bit. First of all here is the whole jQuery Code I used to create the effect:

function mainmenu(){
$(" #nav ul ").css({display: "none"}); // Opera Fix
$(" #nav li").hover(function(){
		$(this).find('ul:first').css({visibility: "visible",display: "none"}).show(400);
		},function(){
		$(this).find('ul:first').css({visibility: "hidden"});
		});
}

 $(document).ready(function(){
	mainmenu();
});

Step by step description:

$(" #nav ul ").css({display: "none"}); // Opera Fix

This is a small fix for Opera, which doesn’t hide the menus fast enough, if you hover above them and so creates a flickering effect. $(” #nav ul “) is the jQuery way to select all unordered lists in #nav. Usage of this is similar to selecting CSS elements. The .css({display:”none”}) sets the display attribute for all Unordered lists to none.

$(" #nav li").hover(function(){ // here goes mouse over effect
                  },function(){ // here goes mouse out effect
                               });

This is the jQuery function for hovering. Really simple to use: first function lets you define what happens when you hover over a specific item( in our case a list item), second function is used for the mouse out event.

$(this).find('ul:first:hidden').css({visibility: "visible",display: "none"}).show(400);

This function finds the first hidden unordered list within the currently hovered list item and shows it. The function “.show” works only under specific circumstances, this is why we set the display to none. The number between the braces defines the animation speed in milliseconds.

$(this).find('ul:first').css({visibility: "hidden"});

This is the mouse out event: we use visibility instead of display since the show function mentioned above, sets display to “block” at the end of the animation. This way if you would hover just a short moment over the item the item would not display for the ongoing animation and then pop out all of a sudden. Using visibility prevents this flickering.

 $(document).ready(function(){
	mainmenu();
});

This function will call our mainmenu() function as soon as the html document is ready.

We are done now, if you can’t get this to work for any reason, here is a working example for download:

Download
Includes: HTML File, CSS File, Javscript Files

Have fun creating your own menu ;)

Update :

I have added some small fixes to the script which were suggested by some of my readers, so thanks for adding improvements. ;)

Another thing I wanted to note: I have seen this script in many templates and wordpress themes now, often used in conjunction with one of wordpress automatic list generating features (wp_list_categories, wp_list_pages)
If you use it that way wordpress adds a title attribute to each link, which can create a flickering effect when the browser tooltip for the title appears. If you are using the script this way you should add the following line of jquery at the beggining of the script to remove the titles:

$(" #nav a").removeAttr("title");

(Last updated 18.09.2008)

Tags: , ,
398 replies
« Older CommentsNewer Comments »
  1. Kriesi
    Kriesi says:

    Since we want to make the menu work, even with javascript disabled we need these styles to show and hide the different sublevels.

    But its true, once javascript is enabled and jQuery is used to display those sublevels these styles are no longer needed.

  2. Michael Sky
    Michael Sky says:

    Hi, great menu. I’ve been using the dropdown successfully for a while. Just added a flyout to the page and it’s working perfectly on all browsers, except for IE7 (I don’t test for IE6 any more).

    On IE7 both menus function perfectly except on the flyout, when you mouse down a list of items, the flyout menu closes when you move over the next main menu item.

    Tried adding z-index to various pieces but hasn’t worked.

    Might be something in the jquery script. All I did for the flyout is copy the code for the drop down and then change the div names.

    Anyway, it’s happening on the above link, the left hand menu, Item 2,

    thanks, Michel Sky

  3. Kriesi
    Kriesi says:

    looks like a CSS problem to me.

    From what I can tell from previous implementations I did, you must be very careful with margins, paddings, borders and floats, both in IE6 and 7. Hope this helps…

  4. Vinny
    Vinny says:

    Hi,

    Great to start to get going with jQuery
    I really like this menu and will start to use it.

    @ Michael Sky
    I changed the last part of the style to
    #nav li ul ul {
    left:12em;
    margin:0px 0 0 0px;
    }

    to get it running even in IE7. That IE7 did not take the 2nd lvl menupart is because of the gap with the 10px. You don/t see this gap in Firefox

  5. Andrés
    Andrés says:

    Hey Kriesi,

    First of all, thanks! great menu, and the guide was really helpful. There is one thing though I have ran into that I haven’t been able to fix. In IE7, when JavaScript is disabled, the dropdown shows up to the right of the menu title and is rendered useless. If JavaScript is enabled, it works fantastic. FF3 works like a charm too. Is there a chance you could tell me if I did something wrong? http://www.luzvenidera.com.ar/webnueva/menu.php – Thanks a lot again!

  6. Daryl F
    Daryl F says:

    Hi Kriesi:

    Like the rest of your readers I also thank you for the time and effort you’ve put into this tutorial, to help us all out. I like the simplicity of the code and the quickness of the response.

    I have a simple question. How do you get the main menus aligned in the center of the page. I need a top menu of about 10pixels and then centered horizontally with a width of 800 pixels.

    Thank you for your time in responding to my question,

    Daryl
    Vancouver, Canada

  7. Kriesi
    Kriesi says:

    Hi Daryl,

    You can center the menu by applying a fixed width to #nav and then set its margin property to:

    margin: 0 auto;

  8. George Mandis
    George Mandis says:

    This works beautifully in every browser for me except for the IEs, including IE7. You can view my implementation at:

    http://www.snaptortoise.com

    Anyone have any ideas? I’m having a problem similar to what Michael Sky described above, with the submenu disappearing when the cursor leaves the main containing LI.

    I would love any suggestions.

  9. Kriesi
    Kriesi says:

    basic solution would be:

    $(” #nav li”).hover(function(){
    $(this).find(‘ul:first’).slideDown(400);
    },function(){
    $(this).find(‘ul:first’).slideUp(400);
    });
    }

    didn’t test it except for firefox, I leave this up to you ;)

  10. Fimbot
    Fimbot says:

    Beautiful script, but drop-down menus like this are notorious for having major usability issues. They aren’t at all user friendly and they hide content to the point that people rarely click on most of the links inside the dropdown menus. So, while it looks really cool, I would add caution in using this script. Unless of course you don’t care about people being able to navigate your site, then by all means use it!

  11. Anthony
    Anthony says:

    I highly enjoyed your site. My question is where can I get a tutorial to do a menu like on this web page? That’s what I would like to ask if I may?

  12. jwright
    jwright says:

    one thing id like to ask is if you could have the box stay there after you mouse over it fer maybe 1 sec after you take the mouse away just so you it would be a lot easiyer just so it dont hide box instantly after you move the mouse off of the menu

  13. Rafael Masoni
    Rafael Masoni says:

    Wow! Thank you very much!

    I wanted something simple that would work when JavaScript is disabled. I tried so many complicated scripts but none of them worked for me.

    Thanks to you, now I can go on with the project!

  14. Kyle
    Kyle says:

    I love your menu it is the cleanest I have seen. I want to use it but would like to have the color continue to the right the rest of the way across my page. a good visual element separator. I am kinda a novice at css+html. Can I use an and make it fill the rest of the space to the right?

  15. jason
    jason says:

    Wow, good stuff!

    Had one question though… maybe 2… if you feel so inclined. This is more along the lines of a CSS question i think but is it possible to have the top level nav “remain” highlighted with the a:hover style at the same time the sub menu is being rolled over? I’m hoping this may give a clear order to the hierarchy of the nav.

    Also i tried adjusting the background color of the Top : Sub : & Sub 2 menus to be different for each other but not sure i went about this in the best way. The only way i could get it to work was to assign a .top_parent / .parent / and .child CSS attribute. This ended up being allot of fiddling with the XHTML list. Is there a simpler way to do this?

    Regardless on if you choose to answer, thanks so much for teaching such a great navigation!

  16. Kriesi
    Kriesi says:

    @ Anthony: I will write one soon if I can spare some time

    @jwright; would be possible with setTimeout(). I am sure you will find some nice tutorials out there which can teach you how to use it ;)

    @Kyle: I am not sure if i understand, you want the main menu to fill the whole screen-width? just add
    #nav{
    float:left;
    width:100%;
    background-color:#333;
    }

    @Jason: Since CSS offers no way to select a parent or ancestor of an element I think its impossible to let the top level nav remain highlighted without using javascript.

    Your second problem is solved rather easy: you don’t need additional html classes, you can just use:

    #nav a { // top level
    background-color:#345678;
    }
    #nav ul a { // second level
    background-color:#23ab45;
    }
    #nav ul ul a { // third level
    background-color:#123456;
    }

    For each additional sublevel add a unordered list.

  17. jason
    jason says:

    Thanks Kriesi for the help!

    Thats much more straight forward then what i was doing!

    I stumbled upon this CSS drop down (http://phoenity.com/newtedge/horizontal_nav/) that appears to achieve the top level highlight / utilizing tables i think? – not 100% on this. Some major draw backs however is you’re limited to one level drops and the biggest draw back is that it doesn’t appear to work well with your script!? I thought this might be the work around but unfortunately it appears only to add more problems. Que Sera, Sera..

    As for yet another adjustment… i’m making the sub drops that are on the far right side of the nav float to the left in order to contain the nav within a set area and avoid breaking the boundaries of a site’s layout. I noticed by adjusting the css…

    #nav ul {
    position:absolute;
    width:12em;
    top:1.5em;
    display:none;
    right: 0; // Flushes to right side (However doesn’t exactly line up?)
    }

    #nav li ul ul {
    margin:0px 0 0 10px;
    right: 12em; // Flushes Sub Menu to right side (Alignment still a bit off?)
    }

    I suppose if i assign this a selector like – .farRightNav – i could feasibly apply this anywhere needed…
    Maybe?

  18. delang
    delang says:

    maybe how about to make the child appear on left side when it reach end of browser?

    how you get what i said.
    thanks again

  19. SEOAdsenseThemes.com
    SEOAdsenseThemes.com says:

    Currently I’m building a WordPress theme that use Stu Nicholl’s CSSPLay Drop Down menu which is purely a CSS solution. And the fact that it is a proven cross-browser pure CSS solution is great.

    But I began to use jQuery in my theme, so I’ve made a decision to finally ditch it and go with a jQuery drop down menu considering jQuery is already in use.

    So thanks for the tutorial, it came in pretty handy for me.

  20. SEOAdsenseThemes.com
    SEOAdsenseThemes.com says:

    Oh yes, I had to replace all ‘$’ with ‘jQuery’ in the javascript file to make it work in WordPress.

    I can’t explain why the workaround in WordPress coz I’m a stranger to jQuery. Does anyone know why?

  21. Krlos
    Krlos says:

    VERTICAL MENU ADAPTATION

    .verticalMenu ul{
    margin: 0;
    padding: 0;
    list-style-type: none;
    /* width: 160px; */ /* Width of Menu Items */
    border-bottom: 1px solid #ccc;
    }

    /* Create the box context for the submenues */
    .verticalMenu ul li{
    position: relative;
    }

    /*Sub level menu items */
    .verticalMenu ul li ul{
    position: absolute;
    /*width:*/ 170px; /*sub menu width*/
    top: 0;
    /* visibility: hidden;*/
    }

    /* Menu links */
    .verticalMenu ul li a{
    display: block;
    overflow: auto; /*force hasLayout in IE7 */
    color: black;
    text-decoration: none;
    background: #fff;
    padding: 1px 5px;
    border: 1px solid #ccc;
    border-bottom: 0;
    }

    .verticalMenu ul li a:visited{
    color: black;
    }

    .verticalMenu ul li a:hover{
    background-color: yellow;
    }

    /* ——————————————————– */

    JAVASCRIPT

    function mainmenu(){

    $(‘.verticalMenu ul li ul’).width($(‘.verticalMenu ul’).width());

    $(” .verticalItem ul “).css({display: “none”}); // Opera Fix
    $(” .verticalItem li”).hover(
    function(){
    $(this).find(‘ul:first’).css({visibility: “visible”,display: “none”, left: $(this).width() + ‘px’ }).fadeIn(400);
    },function(){
    $(this).find(‘ul:first’).css({visibility: “hidden”});
    });

    }

    $(document).ready(function(){
    mainmenu();
    });

Trackbacks & Pingbacks

  1. […] Anyways, I visited Christian Budschedl’s web site tonight to finally understand CSS menus.  Its a great tutorial.   I also appreciate it when companies like AirTight Interactive share free photo viewers […]

« Older CommentsNewer Comments »

Comments are closed.