Creating a Multi-Level Dropdown Menu using CSS and jQuery
In this post i will teach you how to create a Multi-Level Dropdown Menu using CSS and jQuery
The menu must:
- Support an infinite number of levels
- Support menu items of variable width
- Have a minimal ammount of CSS and Javascript code
- Work across all major browsers including IE6 (although it won’t have the slide effect on it)
Let’s start by writing the HTML that will define the structure of menu
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | <ul id="menu" class="clear"> <li><a href="#">Item 1</a> <ul> <li><a href="#">Item with a long title 1.1</a></li> <li><a href="#">Item 1.2</a></li> <li><a href="#">Item 1.3</a></li> <li><a href="#">Item 1.4</a></li> </ul> </li> <li><a href="#">Item 2</a></li> <li><a href="#">Item 3</a></li> <li><a href="#">Item 4</a> <ul> <li><a href="#">Item with a very loooooooooooooooooooong title 4.1</a></li> <li><a href="#">Item 4.2</a> <ul> <li><a href="#">Item with a very long title 4.2.1</a></li> <li><a href="#">Item 4.2.2</a></li> <li><a href="#">Item 4.2.3</a></li> <li><a href="#">Item 4.2.4</a> <ul> <li><a href="#">Item with a very loooooooong title 4.2.3.1</a></li> <li><a href="#">Item 4.2.3.2</a></li> <li><a href="#">Item 4.2.3.3</a></li> <li><a href="#">Item 4.2.3.4</a></li> </ul> </li> </ul> </li> <li><a href="#">Item 4.3</a></li> <li><a href="#">Item 4.4</a></li> </ul> </li> </ul> |
As you can see this is very simple, just some nested unordered lists representing each group of items from the menu
Now let’s begin styling the menu
1 2 3 4 5 6 7 8 9 | .clear { height: 100% } .clear:after { content: ''; display: block; clear: both } #menu, #menu ul { list-style: none; margin: 0; padding: 0 } #menu li { background: #bdd2ff; border-right: 1px solid #fff; float: left; white-space: nowrap } #menu li a { display: block; padding: 5px 20px; text-decoration: none; color: #13a } #menu ul { display: none } |
We put float: left on #menu li so that each li stay side by side, the a uses display: block so that it occupies the whole li, white-space: nowrap is used to prevent the text of each item from wrapping to the next line. We also hide any secondary menus, we will style them later
The .clear class is a class i use so that you don’t need to put an element with clear: both after floated elements. It does that by using CSS to insert an element with clear: both after the matched element. The height: 100% does this trick for IE
Your menu should look like this now:

Now let’s style the secondary menus
First add position: relative to line 6 from previous code
1 | #menu li { background: #bdd2ff; border-right: 1px solid #fff; position: relative; float: left; white-space: nowrap } |
Then add this
1 2 3 4 | #menu ul { background: #fff; position: absolute } #menu ul li { background: #aabde6; border-top: 1px solid #bdd2ff; border-right: 0px solid transparent; float: none } #menu ul ul { top: -1px; left: 100% } |
We put float: none on the lis because they will stay one below the other now. We also set position: absolute on each ul. What position: absolute does is position an element absolutely in the screen relative to the first parent element that has a position other than static, in our case their parent li which we put position: relative on it. The background declaration on the ul is to prevent a bug in IE
The first level ul don’t need anything else, it will be already positioned correctly. The second and further level ul will need to have it’s position changed. By setting top: 0px we make them have their top position right where their parent li starts. By setting left: 100% we make them have their left position at 100% the size of their parent li. We change top to -1px later because of the border-top 1px in the li
All the menus should be positioned correctly now:

We now need to use javascript to show and hide each menu window when the user hovers their parent li
First add display: none to each menu ul so that they start hidden
1 | #menu ul { background: #fff; display: none; position: absolute } |
Now let’s start coding javascript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | $(document).ready(function() { $('#menu').menu(); }); var ie = $.browser.msie && $.browser.version < 8.0; $.fn.menu = function() { $(this).find('li').hover(function() { $(this).addClass('hover'); ie ? $(this).find('> ul').fadeIn() : $(this).find('> ul').slideDown(250); }, function() { $(this).removeClass('hover'); ie ? $(this).find('> ul').fadeOut() : $(this).find('> ul').slideUp(250); }); } |
In line 1 we call the jQuery ready function, which is called when the page is completely loaded. In line 2 we select the element in the page which has the id = menu and calls the menu function we define below
Line 4 declares a boolean variable to indicate if the browser is an old version of IE. We will need to make some IE specific code later
Line 8 finds every li on the menu and calls the hover function. The hover function receive two arguments, the first is a function that will be executed when the mouse enters the element, the second is executed when the mouse leaves the element
In the first function we make the first children ul appear. We do this using the slideDown function which receives the an integer as the duration of the slide down effect. This code doesn’t work (in this specific case of this menu) on older versions of Internet Explorer so we will use the less cool fadeIn effect for it. We also add the class hover to the li
The second function does the same thing, except it makes the first children ul disappear by sliding up or fading out and removes the class hover
Now let’s add some CSS to change the background of the lis that have the class hover
1 | #menu li.hover { background-color: #cfdeff } |
Our menu is working nicely now, but there is a lot of things to do to improve it. First let’s add an arrow indicating that a li has child items. Add this after line 7 of previous code
1 2 3 4 5 | $(this).find('li').each(function() { if ($(this).find('> ul').size() > 0) { $(this).addClass('has_child'); } }); |
What it does is for each li in the menu, check if it has a child ul and if true, add the class has_child to the li
And now we add this css to style the lis that have childs
1 | #menu li.has_child { background-image: url('down.gif'); background-position: right center; background-repeat: no-repeat; padding-right: 10px } |
Now add this line to make the uls have a nice shadow. Unfortunately this doesn’t work in Internet Explorer
1 | #menu ul { -webkit-box-shadow: 3px 3px 4px #999; -moz-box-shadow: 3px 3px 4px #999; box-shadow: 3px 3px 4px #999 } |
Our menu is almost done. The only problem now is that as soon as the mouse leaves the menu it hides it. This is bad for usability. We need to have a timer that will count if the mouse has left the menu for 500 milliseconds, then hide it. To do that change this:
1 2 3 4 5 6 7 | $(this).find('li').hover(function() { $(this).addClass('hover'); ie ? $(this).find('> ul').fadeIn() : $(this).find('> ul').slideDown(250); }, function() { $(this).removeClass('hover'); ie ? $(this).find('> ul').fadeOut() : $(this).find('> ul').slideUp(250); }); |
to this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | var closeTimer = null; var menuItem = null; function cancelTimer() { if (closeTimer) { window.clearTimeout(closeTimer); closeTimer = null; } } function close() { $(menuItem).find('> ul ul').hide(); ie ? $(menuItem).find('> ul').fadeOut() : $(menuItem).find('> ul').slideUp(250); menuItem = null; } $(this).find('li').hover(function() { cancelTimer(); var parent = false; $(this).parents('li').each(function() { if (this == menuItem) parent = true; }); if (menuItem != this && !parent) close(); $(this).addClass('hover'); ie ? $(this).find('> ul').fadeIn() : $(this).find('> ul').slideDown(250); }, function() { $(this).removeClass('hover'); menuItem = this; cancelTimer(); closeTimer = window.setTimeout(close, 500); }); |
That’s a lot of code! Instead of hiding the menu immediately, the second function of the hover sets a timeout that will execute the close function after 500 milliseconds. The first function of the hover is also different, it cancels the timer and checks if the exited menuItem is different from the current hovered item or a parent of it, if true it instantly closes the current exited item.
That’s it. The menu is finished. But wait, let’s look at it on Internet Explorer 6

To fix these issues add this code to the menu function
1 2 3 4 | if (ie) { $(this).find('ul a').css('display', 'inline-block'); $(this).find('ul ul').css('top', '0'); } |
Unfortunately setting the a as display: inline-block makes them not occupy the whole area of the li. If anyone has a better solution feel free to post in the comments
Now our menu is finished. Take a look at a screenshot of it on a non retarded browser

Hope you liked this tutorial. If you have any questions post a comment and I will try to answer
Hi
How about using a DIV instead of the A tag in the LI’s? Imagine you want to have a couple of images and some text in every LI item. This way, the menu is not rendering correctly and is breaking up. Any ideas please?
Thanks
Ian
How can I target each level background-colours and text colours? ie: top level must be brown font, level 2/3/4 must be orange