You are on page 1of 6

Creating a Multi-Level Dropdown Menu using CSS and jQuery

December 11th, 2010 victorcisneiros Leave a comment Go to comments In this post i will teach you how to create a Multi-Level Dropdown Menu using CSS and jQuery

View Demo
The menu must:

Download Files

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 wont have the slide effect on it) Lets 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 <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>

32 33 34

</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 lets 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 dont 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 lets 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 #menu ul { background: #fff; position: absolute }

2 #menu ul li { background: #aabde6; border-top: 1px solid #bdd2ff; border3 right: 0px solid transparent; float: none } 4 #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 dont need anything else, it will be already positioned correctly. The second and further level ul will need to have its 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 lets 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 doesnt 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 lets 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 lets add an arrow indicating that a li has child items. Add this after line 7 of previous code
1 $(this).find('li').each(function() { if ($(this).find('> ul').size() > 0) { 2 $(this).addClass('has_child'); 3 4 } 5 });

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 doesnt 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:
$(this).find('li').hover(function() { $(this).addClass('hover'); 1 ie ? $(this).find('> ul').fadeIn() : $(this).find('> 2 3 ul').slideDown(250); }, function() { 4 $(this).removeClass('hover'); 5 ie ? $(this).find('> ul').fadeOut() : $(this).find('> 6 7 ul').slideUp(250); });

to this:
var closeTimer = null; var menuItem = null; 1 2 function cancelTimer() { 3 if (closeTimer) { 4 5 window.clearTimeout(closeTimer); closeTimer = null; 6 7 } 8 } 9 function close() { 10 11 $(menuItem).find('> ul ul').hide(); 12 ie ? $(menuItem).find('> ul').fadeOut() : 13 $(menuItem).find('> ul').slideUp(250); menuItem = null; 14 15 } 16 $(this).find('li').hover(function() { 17 18 cancelTimer(); 19 var parent = false; 20 $(this).parents('li').each(function() { 21 if (this == menuItem) parent = true; 22 23 }); if (menuItem != this && !parent) close(); 24 25 $(this).addClass('hover'); 26 ie ? $(this).find('> ul').fadeIn() : $(this).find('> 27 28 ul').slideDown(250); }, function() { 29 $(this).removeClass('hover'); 30 menuItem = this; 31 32 cancelTimer(); 33 closeTimer = window.setTimeout(close, 500); });

Thats 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. Thats it. The menu is finished. But wait, lets 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

You might also like