How To Code a Vertical Accordion Nav Menu with jQuery
Published in Tutorials, Web DevelopmentWebsite navigation is an important aspect to any functioning layout. Users will often be looking for methods to traverse over your pages, and sometimes this requires a bit of creativity. I love the idea of vertical navigations especially with sub-menu links.
In this tutorial I will demonstrate how we can build a simple vertical navigation accordion menu using CSS3 and jQuery techniques. We can build custom styles and format the links to slide down & up on each click. Using this method we can also build sub-menu links, splitting up headers by ID or class names. Follow along with the ideas below and feel free to download a copy of my source code.

Live Demo – Download Source Code
Document Structure
As with most of my tutorials, I am starting off with the typical HTML5 doctype and other extraneous scripts. These 3rd party docs include the latest jQuery library, the html5shiv document, and a custom Google Webfont used in the page heading.
<!doctype html> <html lang="en-US"> <head> <meta http-equiv="Content-Type" content="text/html;charset=utf-8"> <title>Vertical jQuery Accordion Nav Menu</title> <meta name="author" content="Jake Rocheleau"> <link rel="shortcut icon" href="http://vandelaydesign.com/favicon.ico"> <link rel="icon" href="http://vandelaydesign.com/favicon.ico"> <link rel="stylesheet" type="text/css" href="styles.css"> <link rel="stylesheet" type="text/css" href="http://fonts.googleapis.com/css?family=Merienda:400,700"> <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script> <script type="text/javascript" language="javascript" charset="utf-8" src="nav.js"></script> <!--[if lt IE 9]> <script type="text/javascript" src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> </head>
The other local files I am including are named styles.css and nav.js. All the page styles are fitted inside our stylesheet while I have separated the jQuery code into a new file as well. The actual page HTML is easy to follow since we are building a nav element using unordered list items.
<nav>
<ul id="nav">
<li><a href="#">Animation</a>
<ul>
<li><a href="http:/www.google.com/search?q=design+cartoons+animation">Cartoons</a></li>
<li><a href="http:/www.google.com/search?q=design+comic+strips+inspiration">Comic Strips</a></li>
<li><a href="http:/www.google.com/search?q=how+to+clip+video+footage">Video Clips</a></li>
<li><a href="http:/www.google.com/search?q=design+create+animated+gifs">Web GIFs</a></li>
</ul>
</li>
<li><a href="#">Graphic Design</a>
<ul>
<li><a href="http:/www.google.com/search?q=photoshop+tutorials+graphics+design">Adobe Photoshop</a></li>
<li><a href="http:/www.google.com/search?q=digital+branding+graphics+logos">Branding & Logos</a></li>
<li><a href="http:/www.google.com/search?q=graphics+design+marketing">Digital Marketing</a></li>
<li><a href="http:/www.google.com/search?q=graphic+design+illustrations">Illustrations</a></li>
<li><a href="http:/www.google.com/search?q=infographics+inspiration">Infographics</a></li>
<li><a href="http:/www.google.com/search?q=product+design+inspiration">Product Design</a></li>
</ul>
</li>
The whole block is wrapped inside a <nav> element which is fitted using an unordered list. Each top-tier list item is given a link along with another internal <ul>. This secondary list is the actual list of links which will be displayed. The first set of list items are your navigation labels which behave as containers.
After clicking on each header we will show/hide the internal navigation links. This situation can get tricky when trying to implement sub-navigation and sub-sub-navigation if you want to show/hide those as well. A better solution is to nest a third <ul> element which is also displayed immediately with all the other links, but rendered using additional padding.
Styling Page Elements
The default CSS styles I follow are based on Eric Meyer’s CSS resets with some other custom properties. I always include the box-sizing property along with the vendor-specific prefixes defined using border-box. This forces all margins and padding to be limited at the maximum width and not to add any additional pixels to box containers.
html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
outline: none;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
html { height: 101%; }
body { font-size: 62.5%; line-height: 1; padding-bottom: 65px; font-family: Arial, Tahoma, sans-serif; background: #c5bde5 url('images/bg.png'); }
article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { display: block; }
ol, ul { list-style: none; }
blockquote, q { quotes: none; }
blockquote:before, blockquote:after, q:before, q:after { content: ''; content: none; }
strong { font-weight: bold; }
table { border-collapse: collapse; border-spacing: 0; }
img { border: 0; max-width: 100%; }
h1 { font-family: 'Merienda', 'Trebuchet MS', Verdana, sans-serif; font-size: 2.95em; line-height: 1.7em; margin-bottom: 20px; font-weight: bold; letter-spacing: -0.03em; color: #675d90; text-shadow: 2px 2px 0px rgba(255,255,255,0.65); text-align: center; }
Also I have setup each h1 header element to use our custom "Merienda" font family. Along with some cool text shadows, we can see a very unique effect in the page typography. I have applied similar text shadows onto the accordion text so the links are readable at a glance.
/* nav menu styles */
#nav {
display: block;
width: 280px;
margin: 0 auto;
-webkit-box-shadow: 3px 2px 3px rgba(0,0,0,0.7);
-moz-box-shadow: 3px 2px 3px rgba(0,0,0,0.7);
box-shadow: 3px 2px 3px rgba(0,0,0,0.7);
}
#nav > li > a {
display: block;
padding: 16px 18px;
font-size: 1.3em;
font-weight: bold;
color: #d4d4d4;
text-decoration: none;
border-bottom: 1px solid #212121;
background-color: #343434;
background: -webkit-gradient(linear, left top, left bottom, from(#343434), to(#292929));
background: -webkit-linear-gradient(top, #343434, #292929);
background: -moz-linear-gradient(top, #343434, #292929);
background: -ms-linear-gradient(top, #343434, #292929);
background: -o-linear-gradient(top, #343434, #292929);
background: linear-gradient(top, #343434, #292929);
}
#nav > li > a:hover, #nav > li > a.open {
color: #e9e9e9;
border-bottom-color: #384f76;
background-color: #6985b5;
background: -webkit-gradient(linear, left top, left bottom, from(#6985b5), to(#456397));
background: -webkit-linear-gradient(top, #6985b5, #456397);
background: -moz-linear-gradient(top, #6985b5, #456397);
background: -ms-linear-gradient(top, #6985b5, #456397);
background: -o-linear-gradient(top, #6985b5, #456397);
background: linear-gradient(top, #6985b5, #456397);
}
The nav container itself is given a small box shadow and fitted to a maximum width of 280px. You can obviously adjust this container to fit your layout, which defines the purpose for using the extra nav element. But since we are using CSS3 gradient backgrounds the whole nav block is very fluid.
All the anchor links are targeted using parent-child relationships. The direct parent selectors arrow syntax follows that we only want anchor links directly inside an <li> contained inside the #nav element. This is also true on hover and when the navigation menu is open (via the applied CSS class .open).
Sub-Navigation CSS
One last key point to our stylesheet deals with the internal anchor links. Since each li element contains another <ul> for the links, we need to hide these by default. Then after the user clicks on a container link we display all the internal elements.
#nav li ul { display: none; background: #4a5b78; }
#nav li ul li a {
display: block;
background: none;
padding: 10px 0px;
padding-left: 30px;
font-size: 1.1em;
text-decoration: none;
font-weight: bold;
color: #e3e7f1;
text-shadow: 1px 1px 0px #000;
}
#nav li ul li a:hover {
background: #394963;
}
This is easily accomplished using the display: none; property on any ul element inside the navigation list items. For adding another level of navigation you wouldn’t need to hide anything else, because it will show/hide at the same time as the top links. But these can be targeted as another internal ul element like this: #nav li ul li ul a.
jQuery Accordion Effects
We have the entire block element displaying properly, so now we need to create the JavaScript animations. I have created a new document nav.js and opened the typical jQuery DOM function, checking the page document has finished loading before running any codes.
$(document).ready(function(){
$("#nav > li > a").on("click", function(e){
if($(this).parent().has("ul")) {
e.preventDefault();
}
My jQuery selector is targeting only specific anchor links found directly inside the #nav container. If the anchor has a sibling <ul> element then we know it has a navigation to display, and so we don’t want to load the href value when clicked. Then we need to check if the current link is already open, and if so arrange the other elements appropriately.
if(!$(this).hasClass("open")) {
// hide any open menus and remove all other classes
$("#nav li ul").slideUp(350);
$("#nav li a").removeClass("open");
// open our new menu and add the open class
$(this).next("ul").slideDown(350);
$(this).addClass("open");
}
else if($(this).hasClass("open")) {
$(this).removeClass("open");
$(this).next("ul").slideUp(350);
}
});
});
The first if/else logic check determines if the current anchor does not have the class .open. If so we need to hide any already open menus on the page and then open our newest one. Otherwise we check if the link already has the class and the user clicked it to close. In this scenario we run .removeClass() and .slideUp() on the targeted list element.
You can actually customize the duration values inside the sliding functions. jQuery’s documentation page explains how you can setup the current duration value in milliseconds, and also set a custom easing profile via jQuery UI. For this demo I am using the basic “linear” easing effect. But the jQuery UI library is very small and you may consider using a different easing function in future projects. You can try out examples of these effects to determine if custom easing is something you would want to include.

Live Demo – Download Source Code
Final Thoughts
I hope this tutorial can offer a solution for web developers who need to build relatively simple accordion-style widgets. Any typical website layout is often crammed with important content and lacking in additional space. Sometimes you just cannot fit a horizontal navigation into a fitted place. However you can easily implement this code into a sidebar or floating alongside the page content.
Definitely check out my live demo above and see how this menu works in modern browsers. If you have the spare time download a copy of my source code as well. You are free to play around and customize this to your own liking, and it should work beautifully within any project. Additionally let us know your thoughts or questions on the tutorial in our comments discussion area.
About the Author:
Jake is a freelance writer and frontend web developer. He can be found writing in many blogs on topics such as mobile interfaces, freelancing, jQuery, and Objective-C. Check out his other articles throughout Google and follow his tweets @jakerocheleau. Jake’s Google+ profile.



29 Responses
Bookmarked. Could use this for something. Thank you for sharing Jake.
Neat tutorial , Thanks i got what im looking for !!
Thanks for posting, currently designing a website that needs something like this, will be giving it a try.
Sweet tutorial!
I’m only missing tab navigation. Which isn’t that hard to code myself but maybe it’s something you can add in the future.
Keep up your awesome work!
This is really great- in your code, how would you have the sub-menu display on-hover, rather than on-click? So in your demo, I would want to hover over “Animation” to see its submenu, rather than click it first?
Thanks!
Nevermind- just changed it in nav.js – duh. Thanks for this tutorial!
Love this accordion menu! Thanks for publishing it!
This is going to sound like a dumb question but I’m new to this and I’ve never used jquery codes. Where do I insert the nav.js code?
Also, for some reason I’m getting a bullet next to each header box. For example: to the left of “animation” (outside of the blue header).
Hope this makes sense. Any help would be greatly appreciated!
Thank you for the Vertical Accordion Navigation Menu. I’ve been playing with the nav but was wondering if it is possible to have an arrow pointing to the right next to each main category then when its selected have an arrow pointing downwards?
I have arrows to use but not sure how to code them for the categories.
Thanks and I would greatly appreciate feedback.
Hi Marcy,
If you download the source code from the link within the post you can see how everything should be set up. You can also compare your code to the demo to see what is different, and that should show why you are getting the bullet. You can use a tool like http://prettydiff.com to compare the code.
Thanks Steven!
I figured it out, thanks for the info! I’m now having issues with this not working properly (or at all) in IE8. Not sure about IE7 or 9. It is only showing bulleted lists here.
Also has issues with FF in that the entire body of the website jumps or stretches when clicking on some of the nav headers (the ones with the most submenus)
…. It works the perfectly in Chrome. Any ideas on this?
my url is: http://www.springcreekrusticoutfitters.com
Thanks!
Marcy
Hi Marcy,
I’m not sure if Jake intended it to be compatible with older browsers like IE7 or 8. Our demo is working fine for me in IE9 and FF. Unfortunately, I don’t have the time to troubleshoot your site. When the nav menu (or any code snippet for that matter) is added to a live site it could be some type of code conflict between the snippet and the code of your site.
Hi
This is a nice little script. I have one small issue with it.
If a link does not have a sub menu then it doesn’t do anything.
Ideally if there are no sub menu items it should just link direct to the link address.
I know it the code below but have no idea how to make it stop the link only if there is a child menu.
if($(this).parent().has(“ul”)) {
e.preventDefault();
}
love it but im going to need to take all the gradiance colors off it for my site i just want the txt.
I love your script! I’m trying to add another level to this. Ex: #nav > li > ul > li > ul > li. No matter what I try, it either does not show the links at all on click or else displays all of them as open. The nav.js first line has $(“#nav > li > a”).on(“click”, function(e), I am learning (trying) to learn this and wondering if there maybe needs to be a level added to this to work? A push in the right direction would be very appreciated. Thanks!
The script is amazing! but i have the same problem as Nigel. I only have one li item with a sub menu and 5 li item with a direct link.
I think we need to change the nav.js!
Thanks alot m8!
Great code! Thanks for sharing.
For Lou & Nigel, I resolve the issue remplacing
if($(this).parent().has(‘ul’)) {
e.preventDefault();
}
with
var elem = $(this).next();
if(elem.is(‘ul’)){
e.preventDefault();
}
++
Hi, love your script but indeed what happens for sub sub menus, they just don’t appear
ie: li ul li ul li
what modification code do we need to add to the nav.js
Hi,
I am having some issues with the code, it looks great in your example but on my site, when I click on a header it reverts back to the top of the page, please can you let me know what I need to change so the options open up in a dropdown?
Kind Regards
Thanks Axel.. That did the trick.. I had to change the (‘ul’) to (“ul”) though. I think this page changed the ‘s to slanty ones, which broke it.
Thanks you very much in advance Alex.
After being playing with it, it results very helpful for me, now I’m trying to open the accordion from an external link. I mean, I have the accordion fixed in the leftside of my page, and once I select a link in the frontpage, I’d link to it links to another page where depending on the element selected, displays the new page with that leftside accordion already expanded on that category.
Sorry for my explanation, cheers
This script works well, but I cannot get it to work together with slimbox2!?
Any ideas what the conflict between these two scripts is?
How to keep an accordion menu state after link is clicked?
I am unable to add submenus and sub-sub menus for some reason. The text implies that it works but I can’t make it happen. Seems that Siourox had the same issue. Is there any information on this. I love the menus but this feature is essential or I can’t use it. Thanks much
Hi – is there a way to keep the menu open at a certain level? For example, if I have the menue on multiple pages and I land on an internal page (like if I landed on “Cartoons” in the example above), can I have the menu load with “Animation” open and “Cartoons” highlighted?
Everything works well local, but on the server the sub-menu’s don’t stay open?
I discovered the RewriteRule is causing the problem, but I don’t know why!?
Options +FollowSymLinks -MultiViews
# Turn mod_rewrite on
RewriteEngine On
RewriteBase /
## hide .php extension
# To externally redirect /dir/foo.php to /dir/foo
RewriteCond %{THE_REQUEST} ^[A-Z]{3,}\s([^.]+)\.php [NC]
RewriteRule ^ %1 [R,L,NC]
## To internally redirect /dir/foo to /dir/foo.php
RewriteCond %{REQUEST_FILENAME}.php -f
RewriteRule ^ %{REQUEST_URI}.php [L]
Can accordion header act as a link to a page?
Guus, check above for Alex answer from March 27th, 2013, it will answer your question and works well for me.
Can someone tell me how to have a category open by default to see the sub-categories? (I think my question is the same as Matt)
Right to the point! Thanks!