Latest TweetsOfficial Resources for #Gutenberg Block Editor: digwp.com/2018/12/resources-gu… #WordPress
Perishable Press

jQuery Accordion Menu Tutorial

Russell Taylor Exclusive guest post by .

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 source files at the end of the article.

Check out the 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(https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700);
@import url(https://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 Source Files

Source Files: jQuery Accordion Menu – Version 1.0 (12 KB 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.

Russell Taylor
About the Author Russell Martin is professional web developer and entrepreneur based out of California with over 10 years experience in the industry. His latest project is CSS Menu Maker which provides free drop down menu code and a Wordpress Menu Plugin for the Wordpress community.
Archives
20 responses
  1. To set this up on wordpress, I don’t see a simple way to add a to my top level menu items.

  2. Peter Mumford September 18, 2013 @ 6:11 am

    That should have read “I don’t see a simple way to add a <span> to my top level menu items.

  3. Works great except for one issue. I can’t click on the parent item, it only expands and collapses the submenu.

    How can I change this so if you click on the element it expands/collapses but if I click on the <a> element it takes me to that link?

    • Why would you want to put a link on the parent? It will open the subitems and go directly to a page. The submenu items would be useless. The way you want it, would only work if you reveal the subitems while hovering the parent. But with this you will have to create a fallback solution for (older) touch devices that don’t transform your hover event automatically into a tab event.

  4. Is there any way to keep the menus open for the section your in?

    For example, if I clicked on your Widget menu option the page loads and keeps the Products Menu open.

    • It would take a couple of lines of code. The question is, which lines?

      WordPress menus leave a class of .current-menu-item so it should be a cinch to check for this class and open the menu when ‘document ready’ ..er when you know how..

      • And here it is for my WordPress one at least. So far so good

        /*
        keep current open
        */
        $(document).ready( function() {
        $('#cssmenu ul li.current-menu-item').parent().show();
        $('#cssmenu ul li.current-menu-item ul').show();
        $('#cssmenu li.current-menu-item ul').show();
        });

      • Nick, your a lifesaver! Thanks so much

  5. Hey quick question. I love the drop down menu feature, but I want to use this in a slightly different way. I have a pm system on my site and I want to create an external link that makes a box drop down with the pm form. So I don’t want the text in the box that drops down. I hope that makes sense lol. Thanks for the help!

  6. Is there any way to keep the menus open for the section your in?

    For example, if I clicked on your Widget menu option the page loads and keeps the Products Menu open.

  7. Thanks for this great tutorial man! it works great and is very easy to customize!

  8. Great tut thanks for sharing

  9. When used with ASP.NET Master pages the menu close when hint an item and a content page is loaded. How can I mantain the menu open?
    Thank you for the menu! Very cool!

  10. Just a quick note to say thanks – I used this to make a quick and lightweight solution for a WP problem I was having. Kudos!

  11. Kilgore Trout November 13, 2013 @ 8:09 am

    Is there a way NOT to collapse the menus automatically?

    I commented out these, because from the explanation it seems that’s the code that removes the active class from all other items except the clicked:

    // $('#products-menu li').removeClass('active');
    // $(this).closest('li').addClass('active');

    But it doesn’t do anything, and strangely everything works just as before.

    Thank you for your help!

  12. Great tutorial! I am implementing this on a WordPress website and I am attempting to display two instances of the accordion on the same page using class selectors instead of id’s. I changed the selector from “#cssmenu” to “.cssmenu” . Any recommendations on how to accomplish this would be greatly appreciated.

    • Just to clarify, if I have two accordion sets on the same page, when I click on one accordion set, it closes the second set.

      • I figured it out. I revised the following code from:

        $('.cssmenu ul ul:visible').slideUp('normal');

        to:

        $(this).parents('ul').find('ul').slideUp('normal');

[ Comments are closed for this post ]