The use of expandable and collapsible panels is widespread on the Internet. There are many plugins and code available that allow you to implement various types of expandable content or ‘accordion’ effects which essentially involve the user clicking on a panel heading to reveal the content underneath. Expandable panels are often used as a way to break up content rich pages into more visually appealing sections, where visitors can choose to read more about a particular section if they wish.
This tutorial walks through the processes and code required to create your own version of expandable panels using a mix of HTML, CSS and jQuery. Modifying and styling the panels is easy and the code has been tested on multiple browsers and devices including Internet Explorer 8, 9 and 10 as well as Chrome, FireFox and Safari. The expandable panels are also responsive and fit 100% of the parent container.
Take a look at the expandable panel demo to see what we will be replicating. There are 3 content panels which can be expanded and collapsed and an open/close icon to indicate the selected panel open/close state.
This code also has some optional parameters that can allow the following to be configured:
The HTML for the expandable panels is fairly straightforward. Each of the 3 panels has a container div with the class expandable-panel and unique ID specified by “cp-1”, “cp-2” etc, which the jQuery references. Within each of these panels is a heading div that contains the open/close icon and a content div which contains regular HTML.
<div id="container"> <div class="expandable-panel" id="cp-1"> <div class="expandable-panel-heading"> <h2>Content heading 1<span class="icon-close-open"></span></h2> </div> <div class="expandable-panel-content"> <p>Panel HTML...</p> </div> </div> <div class="expandable-panel" id="cp-2"> <div class="expandable-panel-heading"> <h2>Content heading 2<span class="icon-close-open"></span></h2> </div> <div class="expandable-panel-content"> <p>Panel HTML...</p> </div> </div> <div class="expandable-panel" id="cp-3"> <div class="expandable-panel-heading"> <h2>Content heading 3<span class="icon-close-open"></span></h2> </div> <div class="expandable-panel-content"> <p>Panel HTML...</p> </div> </div> </div>
The class for the main content expandable-panel-content has the margin-top initially set to -999 pixels. This is to avoid the content from being briefly shown before the page has loaded and the JavaScript has determines the height of the content panel and its position relative to its container.
The main expandable-panel class is given the overflow:auto; property which allows the panel to expand as the content div is revealed and will keep the content hidden when it is outside the div boundaries. This container also needs a minimum height setting so that its height isn’t determined by the negative value of the content panel.
The only image used in the CSS is the icon for the open/close status which you can grab here or recreate.
h2, p, ol, ul, li { margin:0px; padding:0px; font-size:13px; font-family:Arial, Helvetica, sans-serif; } #container { width:300px; margin:auto; margin-top:100px; } /* --------- COLLAPSIBLE PANELS ----------*/ .expandable-panel { width:100%; position:relative; min-height:50px; overflow:auto; margin-bottom: 20px; border:1px solid #999; } .expandable-panel-heading { width:100%; cursor:pointer; min-height:50px; clear:both; background-color:#E5E5E5; position:relative; } .expandable-panel-heading:hover { color:#666; } .expandable-panel-heading h2 { padding:14px 10px 9px 15px; font-size:18px; line-height:20px; } .expandable-panel-content { padding:0 15px 0 15px; margin-top:-999px; } .expandable-panel-content p { padding:4px 0 6px 0; } .expandable-panel-content p:first-child { padding-top:10px; } .expandable-panel-content p:last-child { padding-bottom:15px; } .icon-close-open { width:20px; height:20px; position:absolute; background-image:url(icon-close-open.png); right:15px; } .expandable-panel-content img { float:right; padding-left:12px; } .header-active { background-color:#D0D7F3; }
This is where the fun begins! There are 3 functions here. panelinit() initialises the expandable panels and sets the height and position for each panel content. We execute the initialisation function after the page has loaded, to give a chance for any images used within the panels to be loaded so the heights can be better determined.
The main expanding/collapsing function is called when a panel heading is clicked, and animates the margin-top style of the associated content panel to either zero or the starting position depending on whether the panel is open or closed.
The last function resetpanels() is called only if the accordion variable is set to true, which will collapse any existing open panels so only one panel can ever be fully expanded at once.
To change the animation speed, adjust the panelspeed variable (currently set to half a second). If you want a panel to be initially expanded when the page loads, specify the panel number with the defaultopenpanel variable. You will also need to specify the total panels being used using totalpanels.
(function($) { $(document).ready(function () { /*-------------------- EXPANDABLE PANELS ----------------------*/ var panelspeed = 500; //panel animate speed in milliseconds var totalpanels = 3; //total number of collapsible panels var defaultopenpanel = 0; //leave 0 for no panel open var accordian = false; //set panels to behave like an accordian, with one panel only ever open at once var panelheight = new Array(); var currentpanel = defaultopenpanel; var iconheight = parseInt($('.icon-close-open').css('height')); var highlightopen = true; //Initialise collapsible panels function panelinit() { for (var i=1; i<=totalpanels; i++) { panelheight[i] = parseInt($('#cp-'+i).find('.expandable-panel-content').css('height')); $('#cp-'+i).find('.expandable-panel-content').css('margin-top', -panelheight[i]); if (defaultopenpanel == i) { $('#cp-'+i).find('.icon-close-open').css('background-position', '0px -'+iconheight+'px'); $('#cp-'+i).find('.expandable-panel-content').css('margin-top', 0); } } } $('.expandable-panel-heading').click(function() { var obj = $(this).next(); var objid = parseInt($(this).parent().attr('ID').substr(3,2)); currentpanel = objid; if (accordian == true) { resetpanels(); } if (parseInt(obj.css('margin-top')) <= (panelheight[objid]*-1)) { obj.clearQueue(); obj.stop(); obj.prev().find('.icon-close-open').css('background-position', '0px -'+iconheight+'px'); obj.animate({'margin-top':0}, panelspeed); if (highlightopen == true) { $('#cp-'+currentpanel + ' .expandable-panel-heading').addClass('header-active'); } } else { obj.clearQueue(); obj.stop(); obj.prev().find('.icon-close-open').css('background-position', '0px 0px'); obj.animate({'margin-top':(panelheight[objid]*-1)}, panelspeed); if (highlightopen == true) { $('#cp-'+currentpanel + ' .expandable-panel-heading').removeClass('header-active'); } } }); function resetpanels() { for (var i=1; i<=totalpanels; i++) { if (currentpanel != i) { $('#cp-'+i).find('.icon-close-open').css('background-position', '0px 0px'); $('#cp-'+i).find('.expandable-panel-content').animate({'margin-top':-panelheight[i]}, panelspeed); if (highlightopen == true) { $('#cp-'+i + ' .expandable-panel-heading').removeClass('header-active'); } } } } //Uncomment these lines if the expandable panels are not a fixed width and need to resize /* $( window ).resize(function() { panelinit(); });*/ $(window).load(function() { panelinit(); }); //END LOAD }); //END READY })(jQuery);
Update 01/10/2013: Added ‘header-active’ class to highlight the heading of any active and open panels. This can be turned off by setting highlightopen to false
Update: 07/08/2014. Added resize function for responsive expandable panels that don’t have a fixed width. Just uncomment the $(window).resize function in the jQuery above
totally a newb question i know, but what do i need to change so that I can put the jquery code in a separate file?
Thanks
You open a text document and enter the text.
Then you go save as and make sure the extension is file.js then hit save that should work!
Note: when you want to make changes you have to go file open with notepad, then save as again, change from text file to all files and find the new file.js file and save over the top of it.
That’s it.
For some reason the script works when I place it in the page head but not when I save to a separate file and link to the js file. The CSS file links fine but not the js. Any ideas?
I tried implementing this and it didnt work like it should, the panels just appeared with scroll wheels on their ends and clicking does nothing as if the javascript was broken.
Any help?
Sorry I don’t have online as of yet, but i was wondering if it might have something to do with my javascript or html heads since im using an old template to make my site or im not linking correctly.
Thanks for the speedy reply by the way
Is there a simple way that I can change the color of the active header in this accordion? I have spent so much time trying to figure it out. Thank you so much for taking the time to help out in this.
Thanks tom great share!!!!!
Hi.. is there a simple way to have all panels already opened when the page loads instead of just the default panel?
Nevermind I figured it out.. removed if (defaultopenpanel == i) { … }
and left the two lines.
$(‘#cp-‘+i).find(‘.icon-close-open’).css(‘background-position’, ‘0px -‘+iconheight+’px’);
$(‘#cp-‘+i).find(‘.expandable-panel-content’).css(‘margin-top’, 0);
Is it possible to have 2 windows open by default instead of just 1?
I have 4 panels and if possible I would like to have 2 of 4 default to open on a page. I’m fairly new to Jquery and I’ve been poking around to no avail.
Hi- I have an older version of ie and only the last panel clicks and animates…none of the other panels work, but on newer ie, chrome and firefox, they all work great. I have searched and searched for a solution to this but can’t find any…help!
Thanks for this, it’s what I’ve been looking for.
I would like to use a different icon for open and close and also show hover states for open and close. I currently have a sprite for this, with icons 25px apart (vertically). How would I achieve this please?
I should probably also include the sprite positions:
open: 0, 0
open hover: 0, 25
close: 0, 50
close hover: 0, 75
This is great!
I’m just having a little problem when using responsive content. For instance when flipping a handheld from vertical to horizontal, some of the contents at the bottom gets showned.. It ofcourse works after page refresh.
I got around it by setting a fixed height to my content, but I wonder if there is a better solution or something I’m missing. (Wolud like to keep it as repsonsive as possible).
Help needed. Thank you!
HI Tom. Thanks for the great post. I have a question though: Is there anyway to close a panel manually. My page startup correctly with two divs collapsed. A table gets build in the first panel when dong a action on the second panel. The first panel opens up by itself. This all happens with javascript.
Hi Tom.
For some reason, my panels don’t open. The + and the X are no where to be found either. Any ideas?
This works fine on desktop but not working on mobile. I am using exactly the same JS, css and html on our mobile site and it shows up and looks great. Clicking the plus image opens the panels but cannot close them. Once they are opened they cannot be closed although working perfectly on desktop.
I don’t know if it is related but also setting var accordian = true; doesn’t do anything. it still allows all of them to be opened and not just one at a time.
Any suggestions?
Awesome post by the way, exactly what I was looking for 🙂
Hi Tom, I am testing using browserstack so was using ios buy looking on my galaxy note 2, same issue.
Here is the link [retracted]
Coumd it be a jquery conflict?
By the way, your demo works fine so its just mine that has the issue.
Hi Tom. Thank you so much for this! It was super helpful! I am having two slight difficulties with this (I’m not the best with all this stuff).
Problem 1: I set it to automatically load with the first panel open, however when the page loads the open panel doesn’t display the active color (which is blue). Instead, it loads with a black background (which is the color of the inactive/closed panels). You have to close it and reopen it again for it to show the active blue color.
Problem 2: when you close the panel the inside text can still be seen slightly above the heading section. 🙁
Could you please a take a look at this? I’d appreciate it a ton!
Hi Tom. Thank you so much for this! It’s been great. I have two problems I was hoping you could shed some light on?
1: I set it so that one panel loads already open. However, when it loads it doesn’t display the active color (which I set to blue). You have to close the panel and re-open it again to show as blue. How can I fix this so that when it loads the open panel shows the active header color?
2: When I close the panels a bit of text can still be seen above the header. If it helps any, the outline in .expandable-panel doesn’t wrap around just the header––there’s extra room above and below. Not sure if that’s related but it seems like it is. If you could shed any light on these problems i would be so grateful. Thank you again for this! I’ve been looking all over for this!!
Chad
Hi,
Thanks for the code. I just have one problem. I have another two panels that want to put in one panel as sub-panels. Lets say I have panel 1 and inside that I want to have panel 2 and 3… When I close panel 1 it shows panel 3 on top of 2 and 2 on top of 1… What should I do to fix this?
i have applied this code of my html page and its not working…what could be the issue?
Hey, if you post a link to your page then we can troubleshoot.
the jquery which i saved as a javascript file doesnt load
In case anyone was interested, I sorted my problem out. My desktop and mobile code is all in the same page and the javascript detects the device then uses desktop.css or mobile.css.
As it is on the same page, it didn’t cross my mind that I couldn’t have 2 panels with the same id’s. So as I have 7 panels in total (4 on desktop and 3 on mobile), I changed the javascript to:
var totalpanels = 7;
and gave each panel a unique id. Problem solved.
Why the ‘h2’ and ‘p’ classes in your CSS on line 1? I noticed that this conflicts with the existing styles of both h2 and p on my current site. Is there an alternative???
Thank you.
I solved the issue.
Remove:
h2, p, ol, ul, li {
margin:0px;
padding:0px;
font-size:13px;
font-family:Arial, Helvetica, sans-serif;
}
Add “margin: 0px;” to lines 35,44,47 and 50. This compliments better with the CSS on my site. Thank you. Great feature!
Hi Tom,
this is very useful, thank you. Unfortunately, there is a bug in Internet Explorer 11 with the 3rd panel in the example (with the floated image of penguins in it). The lower part of the image and the last line of text overhangs below the defined gray area of the box when collapsed. Funny that this doesn’t occur in IE8. Is there an easy fix?
demo works fine … panels look fine on my webpage … but they don’t open … any idea ?
This was EXACTLY what I was looking for, and worked like a charm! Thanks for structuring the code so neatly. T’was super simple to make style changes. You rock!!
I found this very useful, thanks.
I decided to tweak it a little so I could use it without having to specify how many panels there are. Set jQuery off counting the id attributes for cp-* then turned it into a generic script any page could use and include in the section.
Next I used an array for specifying which panels I wanted open at the start.
jquery.expandingpanels.js
http://pastebin.com/5hhCTBJC
Then I just call:
$(document).ready(panelinit(new Array(‘panel1′,’panel2′,’etc’)));
Hi Tom, thanks for this great resource, I have a quick question, and was wondering if you could advise?
I have this setup to fit to the browser width, and an image occupying the top panel width:100%;.
Because the panel height is set on page load, this means that when you resize the browser wider than it was when the page was loaded, the panel starts to protrude from the top. I’m not totally savvy with JS, so was wondering if you had any suggestions?
here is a link btw, http://s435130782.websitehome.co.uk/op4/
Thanks,
Hey! A bit of google and a lot of staring at the .js file, I think I’ve fixed it, forgive me if its not correct, I haven’t the slightest clue what I’m doing with Javascript.
For anyone having the same issue:
Pop the following in under the ‘function resetpanels(){‘ bit
function panelresize() {
for (var i=1; i<=totalpanels; i++) {
if (currentpanel == i) {
panelheight[i] = parseInt($('#cp-'+i).find('.expandable-panel-content').css('height'));
$('#cp-'+i).find('.expandable-panel-content').css('margin-top', +'0');
} else {
panelheight[i] = parseInt($('#cp-'+i).find('.expandable-panel-content').css('height'));
$('#cp-'+i).find('.expandable-panel-content').css('margin-top', -panelheight[i]);
}
}
}
Then put the following in directly underneath where it says //END LOAD
window.onresize=function(){
panelresize();
That seems to work for me. eg,
http://s435130782.websitehome.co.uk/op5/
Thanks again
One small thing, now when I open a panel, then close it, then resize browser the last panel to be opened jumps open again. Oh well, sorry to plaster your page with messages.
Hi Tom. This is a great tool – thanks for sharing it.
One question regarding the sections. My script dynamically creates a table which I then insert into the expandable-panel-content section using document.getElemendById. However, when I retract the panel, it only retracts a small amount (the height of the original text), leaving most of the panel still visible. Is there a way to dynamically recalculate the amount the panel must contract?
Thanks
Chris
Is there any way we could save the closed and open state in cookie? That could be really useful. Thanks.
Great stuff!
I have three separate pages within a website. Each page has a container div with a number of panels (the container on page 1 has 4 panels, the container on page 2 has 5 panels, and the container on page 3 has 3 panels). If I change the totalpanels value in the js file to 12 and then uniquely identify the panels with the cp-n value from 1-12, then the panels all expand and collapse as you would expect, independently. Each page has its own container div, but they are not uniquely named. Is this the best way to manage this or is there a better way? Thanks!
Works great, thanks for sharing it, just one feature I’d want to add. How would you add buttons or links to “Expand All” and “Collapse All”?
This is exactly the element I need. However, I am using an environment where I only have access to the of the page and cannot call external .js or .css files.
Is this possible to execute this solely within the of the page? If so, would you be able to tell me how I should arrange all the code elements? Thanks!
Sorry, the word “body” is missing from “of the page” in a couple places. I had written it as an HTML tag and it was dropped.
Would this work for nested panels? I have an application that works much like code collapsing that you see in code editors.
A very neat script, thanks! But there is an issue in IE10 – it hides the scrollbars on page load. And when you click on any panel the windows jerks a bit. This issue is limited to IE only. Any suggestions will be appreciated. Thank you!
Thanks for the script. Is it possible to have the content panel stay open when you move the cursor off of the heading panel and onto the content panel. Right now, once you move the cursor off the heading panel, the content panel retracts. Thanks.
This works for me, but I have contents that are very long, I need a way to add a link at the bottom of the panel to close it.
Any help is appreciated
Thanks
Hi Bella, Did you find a way to close the panel at the bottom?
I’m looking for the same thing.
Thanks
David
hi Tom . collapse is working good . i am using collapse . is there any way that it expend from bottom to top ?
Great Work. Can it work like the one discuss in this link: http://stackoverflow.com/questions/1861030/jquery-accordion-opening-a-box-based-on-href
Basically i want to make it active through link. Is it possible here?
Will these work with a fusion chart inside? Charts need a starting height for their parent container when they first render. Will these render a chart in their collapsed state?