Home > CSS, jQuery > Creating a Multi-Level Dropdown Menu using CSS and jQuery

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

View Demo     Download Files

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

Categories: CSS, jQuery Tags:
  1. April 28th, 2011 at 09:35 | #1

    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

  2. Mark
    June 8th, 2011 at 10:08 | #2

    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

  1. December 16th, 2010 at 04:35 | #1
  2. December 25th, 2010 at 04:20 | #2
  3. January 13th, 2011 at 13:38 | #3
  4. August 23rd, 2011 at 08:48 | #4
  5. August 23rd, 2011 at 14:13 | #5
  6. August 23rd, 2011 at 14:13 | #6
  7. August 23rd, 2011 at 22:57 | #7
  8. August 25th, 2011 at 05:42 | #8
  9. August 27th, 2011 at 03:07 | #9
  10. August 28th, 2011 at 17:54 | #10
  11. September 5th, 2011 at 23:55 | #11
  12. September 16th, 2011 at 18:55 | #12
  13. October 17th, 2011 at 00:21 | #13
  14. November 15th, 2011 at 10:04 | #14