A JavaScript Accordion to Show and Hide HTML
Build a JavaScript accordion script that lets visitors show and hide parts of an HTML Web page.
In this tutorial you learn how to create a JavaScript accordion script using the Document Object Model. What's an accordion? Click the link below to find out!
As you can see, a JavaScript accordion is a useful way to squeeze lots of page content into a small space. It consists of a list of items, of which only one is expanded at any one time. When you click on another item's heading, the first item collapses and the second item expands.
Let's see how the accordion is put together.
Creating the HTML
The first thing to do is build the markup. It's nice to keep the HTML as simple as possible, so we'll just use a div element with a class of accordionItem for each accordion item. Within each accordionItem div we'll place two elements: an h2 element for the item's title bar, and a div element for the item's content. Here's the first of the three accordion items as an example:
<div class="accordionItem">
<h2>About accordions</h2>
<div>
<p>JavaScript accordions let you squeeze a lot of content into a small space in a Web page.</p>
<p>This simple accordion degrades gracefully in browsers that don't support JavaScript or CSS.</p>
</div>
</div>
(To add more accordion items, just create additional accordionItem divs in a similar fashion.)
Creating the CSS
The CSS in the page serves two purposes:
- It makes the accordion look pretty and more usable.
- It provides a
.hideCSS class for hiding accordion item bodies.
Here's the CSS used in the example:
body { font-size: 80%; font-family: 'Lucida Grande', Verdana, Arial, Sans-Serif; }
.accordionItem h2 { margin: 0; font-size: 1.1em; padding: 0.4em; color: #fff; background-color: #944; border-bottom: 1px solid #66d; }
.accordionItem h2:hover { cursor: pointer; }
.accordionItem div { margin: 0; padding: 1em 0.4em; background-color: #eef; border-bottom: 1px solid #66d; }
.accordionItem.hide h2 { color: #000; background-color: #88f; }
.accordionItem.hide div { display: none; }
Each CSS rule does the following:
body- This just sets a nice font and font size for the page.
.accordionItem h2- Styles the item heading inside each accordion item.
.accordionItem h2:hover- Makes the mouse cursor change to a pointer when the heading is hovered over. (Doesn't work in IE6 — no biggie.)
.accordionItem div- Styles the content
divinside each accordion item. .accordionItem.hide h2- Styles the item headings for all the collapsed items.
.accordionItem.hide div- Ensures that the bodies of the collapsed items are not shown.
Creating the JavaScript
Now comes the fun part! The basic strategy here is:
- Start by assigning a
toggleItem()onclickevent handler to each of theh2accordion item headings. - Hide all item bodies except the first, so that only the top item is visible when the page loads.
- When an item heading is clicked,
toggleItem()hides all items in the list, then shows the clicked item if previously hidden. This allows the visitor to toggle any item while keeping the other items collapsed.
Why not hide all items except the first by using markup, rather than JavaScript? Because:
- It would require extra markup.
- On non-JavaScript browsers the hidden items would be permanently hidden, rendering them unreadable.
First you need to create a global array to hold the accordionItem divs:
var accordionItems = new Array();
Now you need to create three functions:
init()to set up the accordiontoggleItem()to expand/collapse an accordion itemgetFirstChildWithTagName(), a short helper function used byinit().
These functions are described below.
The init() function
The init() function is triggered by the body element's onload event:
function init() {
// Grab the accordion items from the page
var divs = document.getElementsByTagName( 'div' );
for ( var i = 0; i < divs.length; i++ ) {
if ( divs[i].className == 'accordionItem' ) accordionItems.push( divs[i] );
}
// Assign onclick events to the accordion item headings
for ( var i = 0; i < accordionItems.length; i++ ) {
var h2 = getFirstChildWithTagName( accordionItems[i], 'H2' );
h2.onclick = toggleItem;
}
// Hide all accordion item bodies except the first
for ( var i = 1; i < accordionItems.length; i++ ) {
accordionItems[i].className = 'accordionItem hide';
}
}
This function comprises 3 parts, as follows:
- The first part pulls the accordion item
divsinto JavaScript DOM objects and stores the objects in theaccordionItemsarray. To do this it first callsdocument.getElementsByTagName()to get a list of alldivs in the page. Then it loops through this list, pushing anydivs that have a class of'accordionItem'onto theaccordionItemsarray.Find out about
getElementsByTagName()in Retrieving page elements via the DOM. Theinit()function also accesses theelement.classNameproperty, which is a handy shorthand way of setting or getting an element'sclassattribute. - The second block of code goes through each accordion item
div, assigning anonclickevent handler function calledtoggleItem()to theh2element inside thediv. To locate theh2element it calls a helper function,getFirstChildWithTagName()(described in a moment). - The third chunk of code loops through all the accordion items except the first, setting each
div's class to'accordionItem hide'. Due to the CSS in the page, this has the effect of hiding each item's contentdiv, as well as giving each item'sh2heading an "unselected" style.
Register the init() function as the body element's onload event handler, like this:
<body onload="init()">
This causes init() to run when the page loads.
The toggleItem() function
The toggleItem() event handler is called when an accordion item's heading is clicked on. It deals with the showing and hiding of the accordion items:
function toggleItem() {
var itemClass = this.parentNode.className;
// Hide all items
for ( var i = 0; i < accordionItems.length; i++ ) {
accordionItems[i].className = 'accordionItem hide';
}
// Show this item if it was previously hidden
if ( itemClass == 'accordionItem hide' ) {
this.parentNode.className = 'accordionItem';
}
}
The function:
- Stores the current CSS class of the accordion item whose heading was clicked in an
itemClassstring variable to refer to later. - Loops through all the accordion items, hiding them by setting their class to
'accordionItem hide'. - Shows the clicked item if its previous class was
'accordionItem hide'— that is, if it was previously hidden. This provides the toggling mechanism. To show the item it simply sets its CSS class to'accordionItem'instead of'accordionItem hide'.
Notice that, in an event handler function, this refers to the object that triggered the event (in this case the item heading that was clicked on).
The getFirstChildWithTagName() function
Nearly finished! You just need to create a small helper function, getFirstChildWithTagName(), that retrieves the first child of a specified element that matches a specified tag name. This function is used by init() to retrieve the first h2 element inside each accordionItem div element.
function getFirstChildWithTagName( element, tagName ) {
for ( var i = 0; i < element.childNodes.length; i++ ) {
if ( element.childNodes[i].nodeName == tagName ) return element.childNodes[i];
}
}
The function is very simple. It loops through each of the child nodes of the supplied element until it finds a node with the supplied tag name. When it finds such a node, it returns it.
Find out about the childNodes and nodeName properties in Looking inside DOM page elements.
Putting it all together
That's all there is to building a JavaScript accordion! Take a look at the demo again, and view the page source to see how the HTML, CSS and JavaScript come together to make the accordion.
Follow Elated
Related articles
Responses to this article
20 most recent responses (oldest first):
Thank you for all of the above information. I'm wondering if there is a way to keep the accordion opened if/when you add a link to another page(without opening the new page in another window) and you navigate back to the accordion page.
Thanks,
Debbie
Here's something similar that I wrote for my JavaScript Tabs tutorial (which works in a similar way to the accordion):
http://www.elated.com/forums/topic/4717/#post18005
You'll also need to modify toggleItem() to set the hash in the address bar accordingly, eg:
document.location.hash = (the hash value);
Please would you show me how and where to add a millisecond timer to the code to give a slower reveal?
Thanks again.
Easiest way is to hack a bit of jQuery in there. Use jQuery's slideUp() and slideDown() methods to show/hide the items. Full example:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<!-- This page is copyright Elated Communications Ltd. (www.elated.com) -->
<title>JavaScript accordion example</title>
<style type="text/css">
body { font-size: 80%; font-family: 'Lucida Grande', Verdana, Arial, Sans-Serif; }
.accordionItem h2 { margin: 0; font-size: 1.1em; padding: 0.4em; color: #fff; background-color: #944; border-bottom: 1px solid #66d; }
.accordionItem h2:hover { cursor: pointer; }
.accordionItem div { margin: 0; padding: 1em 0.4em; background-color: #eef; border-bottom: 1px solid #66d; }
.accordionItem.hide h2 { color: #000; background-color: #88f; }
</style>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript">
//<![CDATA[
var accordionItems = new Array();
function init() {
// Grab the accordion items from the page
var divs = document.getElementsByTagName( 'div' );
for ( var i = 0; i < divs.length; i++ ) {
if ( divs[i].className == 'accordionItem' ) accordionItems.push( divs[i] );
}
// Assign onclick events to the accordion item headings
for ( var i = 0; i < accordionItems.length; i++ ) {
var h2 = getFirstChildWithTagName( accordionItems[i], 'H2' );
h2.onclick = toggleItem;
}
// Hide all accordion item bodies except the first
for ( var i = 1; i < accordionItems.length; i++ ) {
accordionItems[i].className = 'accordionItem hide';
$(accordionItems[i]).find('div').slideUp();
}
}
function toggleItem() {
var itemClass = this.parentNode.className;
// Hide all items
for ( var i = 0; i < accordionItems.length; i++ ) {
accordionItems[i].className = 'accordionItem hide';
$(accordionItems[i]).find('div').slideUp();
}
// Show this item if it was previously hidden
if ( itemClass == 'accordionItem hide' ) {
this.parentNode.className = 'accordionItem';
$(this).parent().find('div').slideDown();
}
}
function getFirstChildWithTagName( element, tagName ) {
for ( var i = 0; i < element.childNodes.length; i++ ) {
if ( element.childNodes[i].nodeName == tagName ) return element.childNodes[i];
}
}
//]]>
</script>
</head>
<body onload="init()">
<h1>JavaScript accordion example</h1>
<div class="accordionItem">
<h2>About accordions</h2>
<div>
<p>JavaScript accordions let you squeeze a lot of content into a small space in a Web page.</p>
<p>This simple accordion degrades gracefully in browsers that don't support JavaScript or CSS.</p>
</div>
</div>
<div class="accordionItem">
<h2>Accordion items</h2>
<div>
<p>A JavaScript accordion is made up of a number of expandable/collapsible items. Only one item is ever shown at a time.</p>
<p>You can include any content you want inside an accordion item. Here's a bullet list:</p>
<ul>
<li>List item #1</li>
<li>List item #2</li>
<li>List item #3</li>
</ul>
</div>
</div>
<div class="accordionItem">
<h2>How to use a JavaScript accordion</h2>
<div>
<p>Click an accordion item's heading to expand it. To collapse the item, click it again, or click another item heading.</p>
</div>
</div>
<p><a href="/articles/javascript-accordion/">Return to the JavaScript Accordion article</a></p>
</body>
</html>
More on jQuery animations at:
http://www.elated.com/articles/super-easy-animated-effects-with-jquery/
Cheers,
Matt
I want to use an image/button to show and hide the content, so I would just replace the "H2" with "img" in this bit of code I'm assuming correct?
// Assign onclick events to the accordion item category image
for ( var i = 0; i < accordionItems.length; i++ ) {
var img = getFirstChildWithTagName( accordionItems[i], 'img' );
img.onclick = toggleItem;
}
And then if I want all accordianItem content to hide itself by default, I would just remove this portion of the javascript right?
// Hide all accordion item bodies except the first
for ( var i = 1; i < accordionItems.length; i++ ) {
accordionItems[i].className = 'accordionItem hide';
}
I'm sure I'll figure this out through trial and error, but confirmation is always nice =)
Thanks again
[Edited by myexpression on 11-Oct-10 16:46]
"I want to use an image/button to show and hide the content, so I would just replace the "H2" with "img" in this bit of code I'm assuming correct?"
Probably "IMG", but yes. But why not just style the H2 using a CSS background image? Semantically better, and you won't have to change the JavaScript.
"And then if I want all accordianItem content to hide itself by default, I would just remove this portion of the javascript right?"
If you mean you want to hide the first item too on page load, then change the 1 to a 0:
// Hide all accordion item bodies
for ( var i = 0; i < accordionItems.length; i++ ) {
accordionItems[i].className = 'accordionItem hide';
}
Cheers,
Matt
...just replace this code on the function toggleItem():
function toggleItem() {
var itemClass = this.parentNode.className;
// Hide all items
for ( var i = 0; i < accordionItems.length; i++ ) {
if ( accordionItems[i].className == 'accordionItem hide' ) accordionItems[i].className = 'accordionItem hide';
//if ( accordionItems[i].className == 'accordionItem' ) accordionItems[i].className = 'accordionItem hide';
}
// Show this item if it was previously hidden
//if ( itemClass == 'accordionItem hide' || itemClass == 'accordionItem') {
this.parentNode.className = 'accordionItem';
if ( itemClass == 'accordionItem') {
this.parentNode.className = 'accordionItem hide';
}
//}
}
Thanks again man, this script is awesome...
I removed the
<BODY onload=init()>
from the HTML page and added
window.onload = init;
to the JS file instead.
Do you know why the example doesn't work when I separate the HTML, JS and CSS?
You could try putting the call to init() in the markup after the accordion markup perhaps:
<script>
init();
</script>
Due to restrictions, I have to use IE7. I installed a web developer plugin for IE7 but it's not as good as firebug or safari's web developer stuff. Do you know how to check the error console on IE7?
I will try your code first thing tomorrow and I'll try to paste what the error console says.
Thanks again.
On IE8, just hit F12...
Is there a way to get this script to work without using getElementsByTagName but using getElementByID?
Thanks!
For example, the 5th FAQ may be "What is an XXX" and another page may have "and when you use an XXX" I want to be able to have a link from XXX to the FAQ page and automatically show the answer to question 5 ("what is an XXX").
I hope that makes sense.
Are you referring to dynamically loading in that particular div's contents through AJAX?
Can you please send a screenshot?
http://www.elated.com/forums/topic/4717/#post18005
The accordion code is fairly similar to the tabs code, so it should be simple to integrate.
Post a response
Want to add a comment, or ask a question about this article? Post a response.
To post responses you need to be a member. Not a member yet? Signing up is free, easy and only takes a minute. Sign up now.

