A Simple and Beautiful jQuery Accordion Tutorial

Written by Kevin Liew on 30 Sep 2009
124,725 Views • Tutorials

Introduction

I was about running of ideas in tutorials, picking my own brain, and finally, I've almost forgotten the awesomeness of accordion. Yes, we will be creating a Accordion! Accordion has several characteristics:

  • Normally, the menu is displayed vertically (I have seen a horizontal one though)
  • Click on an item, it will expand its submenu and hide other submenu
  • Usually, an Accordion has indicators to show the state of the menu

So, yes, we will do that with the minimal amount of code, clean html and good looking images.

1. HTML

In this section, we use UL List to form the structure. The first level UL will be the navigation menu, and the second level UL that resides inside each first level UL's LI will be the submenu.

There is some rules over here about the classes and rel attribute, let me explain:

  • .popular, .category, .comment Classes in the anchor element (first level) are used for the styling of the menu.
  • Rel atribute in the anchor element (first level) is used by javascript to add and remove "selected state" aka change the menu image after it was clicked
  • .item class is required for each heading item
  • .last Class is used to remove border bottom for the last item
<ul id="accordion">
	<li>
		<a href="#" class="item popular" rel="popular">Popular Post</a>
		<ul>
			<li><a href="#">Popular Post 1</a></li>
			<li><a href="#">Popular Post 2</a></li>
			<li><a href="#" class="last">Popular Post 3</a></li>
		</ul>
	</li>
	<li>
		<a href="#" class="item category" rel="category">Category</a>
		<ul>
			<li><a href="#">Category 1</a></li>
			<li><a href="#">Category 2</a></li>
			<li><a href="#" class="last">Category 3</a></li>
		</ul>
	</li>
	<li>
		<a href="#" class="item comment" rel="comment">Recent Comment</a>
		<ul>
			<li><a href="#">Comment 1</a></li>
			<li><a href="#">Comment 2</a></li>
			<li><a href="#" class="last">Comment 3</a></li>
		</ul>
	</li>
</ul>

2. CSS

CSS is pretty simple, we are using two UL Lists. So, what we do is, style the first level UL, skin it with images, and after that, style the second UL List and hide it.

Have you heard about CSS Sprite? CSS Sprites are the preferred method for reducing the number of image requests. Combine your background images into a single image and use the CSS background-image and background-position properties to display the desired image segment. Yes, this is the image we are using for this tutorial:

CSS Sprite Menu Layout

And, if you wish to learn more about CSS, you can read my previous posts:

/* First Level UL List */
#accordion {
	margin:0;
	padding:0;	
	list-style:none;
}
	
	#accordion li {
		width:267px;
	}
	
	#accordion li a {
		display: block;
		width: 268px;
		height: 43px;	
		text-indent:-999em;
		outline:none;
	}
		
	/* Using CSS Sprite for menu item */
	#accordion li a.popular {
		background:url(menu.jpg) no-repeat 0 0;	
	}

	#accordion li a.popular:hover, .popularOver {
		background:url(menu.jpg) no-repeat -268px 0 !important;	
	}
		
	#accordion li a.category {
		background:url(menu.jpg) no-repeat 0 -43px;	
	}

	#accordion li a.category:hover, .categoryOver {
		background:url(menu.jpg) no-repeat -268px -43px !important;	
	}
		
	#accordion li a.comment {
		background:url(menu.jpg) no-repeat 0 -86px;	
	}

	#accordion li a.comment:hover, .commentOver {
		background:url(menu.jpg) no-repeat -268px -86px !important;	
	}
		
		
	/* Second Level UL List*/
	#accordion ul {
		background:url(bg.gif) repeat-y 0 0;
		width:268px;
		margin:0;
		padding:0;
		display:none;	
	}
		
		#accordion ul li {
			height:30px;
		}
			
		/* styling of submenu item */
		#accordion ul li a {
			width:240px;
			height:25px;
			margin-left:15px;
			padding-top:5px;
			border-bottom: 1px dotted #777;
			text-indent:0;
			color:#ccc;
			text-decoration:none;
		}

		/* remove border bottom of the last item */
		#accordion ul li a.last {
			border-bottom: none;
		}	

3. Javascript

There are two major sections in this javascript click event:

  • First section: Reset everything back to default. What it does, hide all the submenus and also reset all the arrow to default position.
  • Second section: Display the selected item and change the arrow direction

It gets a little bit tricky in resetting the arrow back to default position. I'm using for each method to loop thorugh the menu, grab its REL and and remove the classes. I think there are different ways to accomplish it. If you do know a better way, please let me know and I will ammend it.

	
$(document).ready(function () {
		
	$('#accordion a.item').click(function () {

		/* FIRST SECTION */
	
		//slideup or hide all the Submenu
		$('#accordion li').children('ul').slideUp('fast');	
		
		//remove all the "Over" class, so that the arrow reset to default
		$('#accordion a.item').each(function () {
			if ($(this).attr('rel')!='') {
				$(this).removeClass($(this).attr('rel') + 'Over');	
			}
		});
		
		/* SECOND SECTION */		
		
		//show the selected submenu
		$(this).siblings('ul').slideDown('fast');
		
		//add "Over" class, so that the arrow pointing down
		$(this).addClass($(this).attr('rel') + 'Over');			
	
		return false;

	});

	
});

Conclusion

Like this tutorials? You can express your gratitude by visiting my sponsors on the sidebar, bookmark it and help me to spread this tutorial to our friends! :) Thanks!

Demo Download
Join the discussion

Comments will be moderated and rel="nofollow" will be added to all links. You can wrap your coding with [code][/code] to make use of built-in syntax highlighter.

55 comments
freelance writing jobs 11 years ago
Great post! Thanks for sharing!
Reply
Simon 10 years ago
Your accordion has the jQuery slideUp/Down bug that makes the bottom of the accordion (or the content below) jump/bounce. Any way to fix this?
Reply
Rob 10 years ago
It works, it works, it works! Thank you
Reply
Rob 10 years ago
This works really well - thank you - but is there a way for the accordion to begin with one selected level open rather than all closed?
Reply
Kevin Liew Admin 10 years ago
#accordion li.selected ul {display:block;}

add that line to your css file and put selected in the level that you want it to be displayed by default.

<ul id="accordion">
<li class="selected">
.......

try that
Reply
Rob 10 years ago
Hi Kevin, I tried that but it does not seem to make a difference - the page still opens with the menu in the default closed position rather than opening with the menu expanded to display the relevant submenu item. Any ideas? Thank you
Reply
Kevin Liew Admin 10 years ago
oops, my bad. try this:

add this to the last line of css:

#accordion ul.selected {display:block !important}

then, put it to the first ul.

<ul class="selected">
Reply
Nathan 10 years ago
How do you make it so that when you click on the main button (popular post, catagory and comments) it would close. Right now the only way to close the slide down is to click on the sub menus.
Reply
Kevin Liew Admin 10 years ago
Change this line to :

if ($(this).children('ul').is(':visible')) $(this).children('ul').slideDown('fast');
Reply
Matias 10 years ago
Thanks for this, it´s a great menu. But this is my first aproach to JavaScript, i don´t understand wich line has to be replaced
Reply
Kevin Liew Admin 10 years ago
oops, line 21. so, if the UL list is visible, it will not do the slide down again.
Reply
Tyler 9 years ago
I see this code you included : [ if ($(this).children('ul').is(':visible')) $(this).children('ul').slideDown('fast'); ? ]
and i see that you said :oops, line 21.

But replacing line 21 with the code mentioned just breaks it. Can you list the full <script /> section with it included so that when you click on an already open section it will close? Or give a bit of instruction on doing so? Cheers! great post by the way!
Reply
JB 10 years ago
I would like to know how to deploy the menus to open on mouse over and if one can have another menu to slide out of the submenus to the right.
Reply
casey 10 years ago
The links did not work on this as well when I plugged it into wordpress. I fixed it by adding a conditional statement.

var $target = $(event.target);
if ( $target.is('ul li a') ) {
return true;
}else{
return false;
};

This allows a link to work if its a lower ul, if its an upper li however it causes the opening of the accordion. also had to add this to the beginning to make them all close:


$('#accordion li').children('ul').hide();
Reply
Paul 9 years ago
casey, сould you share your css?
Reply
msn 10 years ago
Is there a way for the accordion to begin with one selected level open rather than all closed? Tried using this #accordion ul.selected {display:block !important}

then, put it to the first ul.

<ul class="selected">

But not seems to be working
Reply
Pavel 9 years ago
I'm having difficulties adjusting the CSS, I have classes assigned to LI, not A as in your example. Is it possible to do it that way?
<li class="popular">
<a href="#" rel="popular">Popular Post</a>
<ul>
<li><a href="#">Popular Post 1</a></li>
<li><a href="#">Popular Post 2</a></li>
<li><a href="#" class="last">Popular Post 3</a></li>
</ul>
</li>
Reply
Kevin Liew Admin 9 years ago
it is possible, the way you style it will be:

li.popular a {all the style for the label}
li.popular li a {you will need to clear all the style from the first line, it will get inherited}
Reply
Mark 9 years ago
Hi I'm a beginner. I got the menu working in my side but I don't understand how to use the selection function. When I put in a link at href and I click a item in the list, the menu goes back to his original position. How do I link my other pages to the navigation? Can anybody please explain it without showing only code? Thanks for helping.
Reply
Kevin Liew Admin 9 years ago
Hi Mark, I have updated the tutorial to fix that issue. I should have updated it ages ago. Please download the file again.
Reply
MK 9 years ago
Fixed Version

$(document).ready(function () {

$(' #accordion li').children('ul,p').hide();

$('#accordion li').click(function () {
$(this).children('ul,p').slideUp('normal');

$('#accordion li > a').each(function () {
if ($(this).attr('rel')!='') {
$(this).removeClass($(this).attr('rel') + 'Over');
}
});

if($(this).children('ul,p').is(':hidden') == true) {

$(this).children('ul,p').slideDown('normal');
$(this).children('a').addClass($(this).children('li a').attr('rel') + 'Over');

return false;
}

});

});
Reply
Tyler 9 years ago
Bad UX here, can you make it so the when one opens the others close, but when you click on an open one, it will close without reopening and without causing the whole page to reload? (FFv11 Mac)
Reply
Tyler 9 years ago
I fixed the UX to be more usable. This works the same as intended, with the addition of when you click on an open one, it closes. :)

$('#accordion a.item').click(function (e) {
//remove all the "Over" class, so that the arrow reset to default
$('#accordion a.item').not(this).each(function () {
if ($(this).attr('rel')!='') {
$(this).removeClass($(this).attr('rel') + 'Over');
}
$(this).siblings('ul').slideUp("slow");
});

//showhide the selected submenu
$(this).siblings('ul').slideToggle("slow");

//addremove Over class, so that the arrow pointing downup
$(this).toggleClass($(this).attr('rel') + 'Over');
e.preventDefault();
});
Reply
Art 8 years ago
Thanks for code, it works
Reply
Christian 9 years ago
Hello There, nice Script !

I use it with mouseover:

$(document).ready(function () {

$('#accordion a.item').mouseover(function () {

//slideup or hide all the Submenu
$('#accordion li').children('ul').slideUp('slow');

//remove all the "Over" class, so that the arrow reset to default
$('#accordion a.item').each(function () {
if ($(this).attr('rel')!='') {
$(this).removeClass($(this).attr('rel') + 'Over');
}
});

//show the selected submenu
$(this).siblings('ul').slideDown('slow');

//add "Over" class, so that the arrow pointing down
$(this).children('a').addClass($(this).children('li a').attr('rel') + 'Over');

return false;

});


});

Can anybody tell me how i can slideUp all when i go outside with the mouse from the #accordion ?
Reply
Shannon 9 years ago
Hi there,

Just wondering if it's possible to keep the menu open while I click through the sub menu links.

Currently what happens when I click a sub menu link is it takes me to the page, but on that new page the menu is closed and I have to reopen it.

Thanks
Reply
Christine 9 years ago
Was there a solution found for this? I'm also interested in having the menu sub menu open when on a sub menu page.
Reply
Oscar Blanco 9 years ago
Hello, and thank you for sharing this useful accordion script.

I'm not a programmer so I'm having a hard time trying to figure out how to make the menu item that is displayed at a time, maintain the "on" look you get from the :hover function.

Meaning, that if you click on it, for instance in the example you have here, the arrow will stay down, on click. Then go back to it's initial state when you click on another menu item.

I've seen the "Over" function on the js, but not the "active" or anything like that.

I hope I made myself clear.
Reply
Oscar Blanco 9 years ago
To clarify further what I'm looking for:

It's ok that the different list items stay opened when clicking on another one. But I would like to at least have the list items that are "opened" to keep their "hover" state. So they look as if they're "turned on" or "active".
Reply
juliet 9 years ago
Hi have you found any solution for this?
Reply
Kevin Liew Admin 9 years ago
hey guys, sorry for the late response. I have fixed the issue. Please re-download.
Reply
dolores 9 years ago
hi - great menu thanks for sharing it..

i was wondering how i might make it so that not only do the main menu items have their open state when you are on that particular page, but also when you are on any of the submenu pages... i'd like the main menu item to stay "on" and the submenu items to stay expanded as long as i'm in that section - either on the main menu or any of it's sub-pages... how would i do this...

i would also like to have "on" states for my submenu items...

Reply
edo 9 years ago
Thank you very much for the code, had long been suffering with another menu in javascript, I am new in this and I have two problems, first when loading the page the menu appears unfolded, only when you clik on any menu item starts working, any solution, I'll be doing something wrong? and the other problem is that I need that when you open the new page the link on the menu that it opens in the same state it was when you clik on link
Reply