jQuery Accordion Menu Tutorial

This is an exclusive guest post by , posted on September 16th, 2013.

In this tutorial I am going to show you how to build a jQuery accordion menu from scratch. Most of the time it is possible to create very functional website navigations with just CSS, but this time we are going to need a little jQuery magic to accomplish the accordion functionality.

For this tutorial I am going to assume that you have some experience with HTML and CSS. I will go over all the code, but the main focus of this tutorial we be on jQuery stuff. You may download the demo files at the end of the article.

Demo

The HTML

The HTML structure we will be using for this menu is pretty straight forward. I am just using the very popular nested unordered list and wrapping the whole thing in a <div>. Notice, though, that I included <span> tags around the text for the first level <li> elements. We will need these later for our expand/collapse icons.

<div id="cssmenu">
	<ul>
		<li><a href="#"><span>Home</span></a></li>
		<li><a href="#"><span>Products</span></a>
			<ul>
				<li><a href="#">Widgets</a></li>
				<li><a href="#">Menus</a></li>
				<li><a href="#">Products</a></li>
			</ul>
		</li>
		<li><a href="#"><span>Company</span></a>
			<ul>
				<li><a href="#">About</a></li>
				<li><a href="#">Location</a></li>
			</ul>
		</li>
		<li><a href="#"><span>Contact</span></a></li>
	</ul>
</div>

The CSS

To kick off our CSS styles we need to import the correct fonts and apply some general CSS resets. For this accordion menu I will be using two fonts, Ubuntu and Open Sans. Both of these are great fonts that you can grab for free from Google. I am using the @import method to include these at the top of the style sheet. There are other ways to include the font style, but if you go this route make sure that the @import statements are the very first lines of your CSS file. Otherwise you will run into problems.

@import url(http://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700);
@import url(http://fonts.googleapis.com/css?family=Open+Sans:400,600,300);

/* Base Styles */
#cssmenu,
#cssmenu ul,
#cssmenu li,
#cssmenu a {
	margin: 0;
	padding: 0;
	border: 0;
	list-style: none;
	font-weight: normal;
	text-decoration: none;
	line-height: 1;
	font-family: 'Open Sans', sans-serif;
	font-size: 1em;
	position: relative;
}
#cssmenu a {
	line-height: 1.3;
}

Next we style the main DIV container for menu. Nothing too fancy here.

#cssmenu {
	width: 250px;
	border-bottom: 4px solid #656659;
	-webkit-border-radius: 3px;
	-moz-border-radius: 3px;
	border-radius: 3px;
}

Now we move on to the main heading for the jQuery menu. Instead of using any extra HTML we can just style the first element of the UL to function as the header. We can easily target this menu item with the :first pseudo class and apply the appropriate styles.

There are a couple of things worth mentioning in this CSS code. To achieve the background color/gradient I am using a combination of CSS linear-gradients and a transparent, textured image overlay. The image called pattern.png is a transparent, repeatable pattern that gives the menu items a little bit of texture. Normally to achieve this look, designers just export the background images with the colors included. Instead, this approach allows you to change the color of this menu by simply editing the CSS. No new images needed.

Also, take note that we are now applying the Ubuntu font style here with the font-family property.

#cssmenu > ul > li:first-child {
	background: #66665e;
	background: -moz-linear-gradient(#66665e 0%, #45463d 100%);
	background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #66665e), color-stop(100%, #45463d));
	background: -webkit-linear-gradient(#66665e 0%, #45463d 100%);
	background: linear-gradient(#66665e 0%, #45463d 100%);
	border: 1px solid #45463d;
	-webkit-border-radius: 3px 3px 0 0;
	-moz-border-radius: 3px 3px 0 0;
	border-radius: 3px 3px 0 0;
}
#cssmenu > ul > li:first-child > a {
	padding: 15px 10px;
	background: url(pattern.png) top left repeat;
	border: none;
	border-top: 1px solid #818176;
	-webkit-border-radius: 3px 3px 0 0;
	-moz-border-radius: 3px 3px 0 0;
	border-radius: 3px 3px 0 0;
	font-family: 'Ubuntu', sans-serif;
	text-align: center;
	font-size: 1.2em;
	font-weight: 300;
	text-shadow: 0 -1px 1px #000000;
}
#cssmenu > ul > li:first-child > a > span {
	padding: 0;
}
#cssmenu > ul > li:first-child:hover {
	background: #66665e;
	background: -moz-linear-gradient(#66665e 0%, #45463d 100%);
	background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #66665e), color-stop(100%, #45463d));
	background: -webkit-linear-gradient(#66665e 0%, #45463d 100%);
	background: linear-gradient(#66665e 0%, #45463d 100%);
}

Below is the rest of the CSS needed for the first level of menu items. Again we are using linear-gradients and our texture image to accomplish the background colors of each menu item. Also notice at the bottom we have some styles for a class called has-sub and active. These classes were not included in our original HTML because we will be adding them dynamically with our jQuery code later.

#cssmenu > ul > li {
	background: #e94f31;
	background: -moz-linear-gradient(#e94f31 0%, #d13516 100%);
	background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #e94f31), color-stop(100%, #d13516));
	background: -webkit-linear-gradient(#e94f31 0%, #d13516 100%);
	background: linear-gradient(#e94f31 0%, #d13516 100%);
}
#cssmenu > ul > li:hover {
	background: #e84323;
	background: -moz-linear-gradient(#e84323 0%, #c33115 100%);
	background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #e84323), color-stop(100%, #c33115));
	background: -webkit-linear-gradient(#e84323 0%, #c33115 100%);
	background: linear-gradient(#e84323 0%, #c33115 100%);
}
#cssmenu > ul > li > a {
	font-size: .9em;
	display: block;
	background: url(menu_images/pattern.png) top left repeat;
	color: #ffffff;
	border: 1px solid #ba2f14;
	border-top: none;
	text-shadow: 0 -1px 1px #751d0c;
}
#cssmenu > ul > li > a > span {
	display: block;
	padding: 12px 10px;
	-webkit-border-radius: 4px;
	-moz-border-radius: 4px;
	border-radius: 4px;
}
#cssmenu > ul > li > a:hover {
	text-decoration: none;
}
#cssmenu > ul > li.active {
	border-bottom: none;
}
#cssmenu > ul > li.has-sub > a span {
	background: url(menu_images/icon_plus.png) 96% center no-repeat;
}
#cssmenu > ul > li.has-sub.active > a span {
	background: url(menu_images/icon_minus.png) 96% center no-repeat;
}

Finally we have the styles for our sub menus. Again, nothing too crazy is happening here. One odd thing you might notice is the content property and its value for the #cssmenu ul ul a:before selector. What this little bit of CSS is doing is inserting the double arrow symbol right before the link text in our sub menus. The \00BB code is the unicode encoded version of that symbol which is the format CSS needs to display it properly.

/* Sub menu */
#cssmenu ul ul {
	display: none;
	background: #fff;
	border-right: 1px solid #a2a194;
	border-left: 1px solid #a2a194;
}
#cssmenu ul ul li {
	padding: 0;
	border-bottom: 1px solid #d4d4d4;
	border-top: none;
	background: #f7f7f7;
	background: -moz-linear-gradient(#f7f7f7 0%, #ececec 100%);
	background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #f7f7f7), color-stop(100%, #ececec));
	background: -webkit-linear-gradient(#f7f7f7 0%, #ececec 100%);
	background: linear-gradient(#f7f7f7 0%, #ececec 100%);
}
#cssmenu ul ul li:last-child {
	border-bottom: none;
}
#cssmenu ul ul a {
	padding: 10px 10px 10px 25px;
	display: block;
	color: #676767;
	font-size: .8em;
	font-weight: normal;
}
#cssmenu ul ul a:before {
	content: '\00BB';
	position: absolute;
	left: 10px;
	color: #e94f31;
}
#cssmenu ul ul a:hover {
	color: #e94f31;
}

Stop and Check

At this point in the jQuery menu tutorial we have setup the HTML structure and built our CSS styles. If you have done everything correctly up to this point your menu should look like the image below:

[ Screenshot: jQuery Accordion Menu ]

If you menu does not look like the above image, circle back around and double check your CSS styles and image file paths.

jQuery Review

Just a little review in case you are new to jQuery. In order for our jQuery code to work you will need to include the main jQuery script from somewhere. There are lots of ways to do this but the easiest is to stick the following code somewhere in your page header:

<script src="http://code.jquery.com/jquery-latest.min.js" type="text/javascript"></script>

The next thing we need to do to make sure our jQuery code runs correctly is wrap all of our code in the document.ready() function. This tells our custom jQuery code to run once the page is fully loaded.

$(document).ready(function(){

	// Custom jQuery code goes here

});

The jQuery

We are now ready to dive into the jQuery. If you remember back to the CSS portion of this tutorial, I mentioned a class called has-sub that would be added via jQuery. The following jQuery code does just that. Using the jQuery :has() filter we can easily check to see if a sub menu is present. If so, we add the has-sub class.

$('#cssmenu > ul > li:has(ul)').addClass("has-sub");

The rest of the jQuery code is all contained within one .click() event callback. Each time a link is clicked in our menu, we will run through a set of checks that will either open or close the appropriate sub menu. Take a look at the code in its entirety, and then I will explain it below:


$('#cssmenu > ul > li > a').click(function() {

	var checkElement = $(this).next();

	$('#cssmenu li').removeClass('active');
	$(this).closest('li').addClass('active');	

	if((checkElement.is('ul')) && (checkElement.is(':visible'))) {
		$(this).closest('li').removeClass('active');
		checkElement.slideUp('normal');
	}

	if((checkElement.is('ul')) && (!checkElement.is(':visible'))) {
		$('#cssmenu ul ul:visible').slideUp('normal');
		checkElement.slideDown('normal');
	}

	if (checkElement.is('ul')) {
		return false;
	} else {
		return true;	
	}
});

The very first line of code inside the .click() callback is setting up a variable for convenience. Each time a link is clicked we will want to look at the very next element in the DOM tree. We grab this element with .next() selector and then assign it to the variable checkElement.

The next two lines of code remove the .active class from all menu items, and then add the .active class to the menu item that was just clicked. If you take a look back at the CSS code you will see that the active class controls whether we show the plus or minus image.

After our .active class checks we have the first if statement. This if statement checks for two things. First to see if the checkElement variable is a UL, and second to see whether or not it is visible. If both of these things are true, it means the clicked menu item has a submenu, and that sub menu is visible. The correct functionality for this situation is to collapse the sub menu and remove the .active class.

The second if statement is very similar to our first. This time we are checking to see if the checkElement variable is a UL and if that UL is not visible. If both these things are true it means that we have clicked a menu item that has a sub menu which is collapsed. The correct functionality in this situation is to collapse the open sub menu, and then expand the sub menu of our checkElement.

Our final IF statement will determine whether to return TRUE or FALSE. By default, a clicked link will naturally want to redirect to another page. Returning TRUE will perserve this default functionality. Returning FALSE will cancel this default functionality and the link will not be followed. What we want to do is expand a sub menu if one is present, if not, then we will want to follow the link inside the HREF. To this this we again simply check to see if our checkElement variable is a UL element and then return TRUE or FALSE accordingly.

Download Demo Files

Demo: jQuery Accordion Menu – Version 1.0 ( zip)

Conclusion

There you have it. If you followed all the steps correctly you should have a working jQuery accordion menu. If you run into any problems let us know in the comments and we will try our best to help you out.