A Simple Accordion
Using Only Vanilla JavaScript, CSS, and HTML
My main goal:
To create a simple accordion without any frameworks or package dependencies from scratch.
Additional details:
It would mimic the Bootstrap accordion and include an auto-close feature.
My motivation:
I was in the midst of reworking one of my project websites to be Bootstrap-free.
Additional details:
I wanted to implement this accordion functionality into a large vertical navbar that is a main focal point on my Uniform Lineup website.
Below is a stripped-down, mini version of the accordion I'll be demoing today.
Logic Explanation
I start with the event handler that needs to be listening for each panel click. I will be attaching a master function to this event handler. This master function will house all of the logic that decides what happens every time a panel is clicked.
Next, there are two distinct actions that I want to accomplish with the accordion. Show hidden content and hide visible content. I need two additional functions, one for each possible action.
Lastly, I must decide how I want to attack the auto-closing of panel content. The easiest way is to tack on a hide all panels function. This way, I won’t have to constantly be tracking the current state of all the clickable panels on the page. I will only need to know what panel was just clicked, and is it currently showing or hiding its content.
JavaScript Variable Declarations
I start by assigning a different classname for each panel's collapsable content div in my HTML.
HTML:
<div id="accordian">
<div class="clickable-panel">
<a href="#" id="panel1" onclick="panelToggle(panel1)">Panel 1</a>
</div>
<div class="collapse panel1">
<!-- collapsable content -->
</div>
<div class="clickable-panel">
<a href="#" id="panel2" onclick="panelToggle(panel2)">Panel 2</a>
</div>
<div class="collapse panel2">
<!-- collapsable content -->
</div>
<div class="clickable-panel">
<a href="#" id="panel3" onclick="panelToggle(panel3)">Panel 3</a>
</div>
<div class="collapse panel3">
<!-- collapsable content -->
</div>
<div class="clickable-panel">
<a href="#" id="panel4" onclick="panelToggle(panel4)">Panel 4</a>
</div>
<div class="collapse panel4">
<!-- collapsable content -->
</div>
</div>
I then reference those classnames and use querySelector to declare my variables in JavaScript.
JavaScript:
const panel1 = document.querySelector(".panel1");
const panel2 = document.querySelector(".panel2");
const panel3 = document.querySelector(".panel3");
const panel4 = document.querySelector(".panel4");
The CSS That Matters
When the page initially loads, I want all of the content areas to be hidden. I’ll be manipulating the max-height CSS property to achieve this. In the HTML, I added the class of collapse to each panel’s content area div element. This sets the max-height to zero, which makes all the content areas appear hidden.
HTML:
<div id="accordian">
<div class="clickable-panel">
<a href="#" id="panel1" onclick="panelToggle(panel1)">Panel 1</a>
</div>
<div class="collapse panel1">
<!-- collapsable content -->
</div>
<div class="clickable-panel">
<a href="#" id="panel2" onclick="panelToggle(panel2)">Panel 2</a>
</div>
<div class="collapse panel2">
<!-- collapsable content -->
</div>
<div class="clickable-panel">
<a href="#" id="panel3" onclick="panelToggle(panel3)">Panel 3</a>
</div>
<div class="collapse panel3">
<!-- collapsable content -->
</div>
<div class="clickable-panel">
<a href="#" id="panel4" onclick="panelToggle(panel4)">Panel 4</a>
</div>
<div class="collapse panel4">
<!-- collapsable content -->
</div>
</div>
CSS:
.collapse {
display: block;
overflow: hidden;
max-height: 0px;
-webkit-transition: max-height 1.5s cubic-bezier(0, 1, 0, 1);
transition: max-height 1.5s cubic-bezier(0, 1, 0, 1);
}
I then create a class of show that can be added and/or removed from each panel's collapsable div element on click with JavaScript. The CSS of this class sets the max-height to something really large so the entire content area will be shown.
CSS:
.show {
max-height: 99em;
-webkit-transition: max-height 2.0s ease-in-out;
transition: max-height 2.0s ease-in-out;
}
The JavaScript Functions
First, I start off with the simple show and hide functions. All these two functions are doing is adding or removing the CSS class of show from the collapsable panel content area that was clicked. Then I have the hideAll function that calls the hide function on every accordion panel on the page.
JavaScript:
show = (panel) => panel.classList.add("show");
hide = (panel) => panel.classList.remove("show");
hideAll = () => {
hide(panel1);
hide(panel2);
hide(panel3);
hide(panel4);
}
Lastly, comes the master function referenced earlier. The panelToggle function first checks if the class of show is attached to whichever accordion panel was just clicked. If it is, that means the panel clicked is visible and the hide function is executed in the if statement and the else portion of the function is ignored.
JavaScript:
panelToggle = (panel) => {
if (panel.classList.contains("show")) {
hide(panel);
} else {
hideAll();
setTimeout (function() {
show(panel);
}, 100);
}
}
If the class of show is not present, the if statement is ignored and the else statement gets executed. All accordion panels will be hidden and a tenth of second later, the clicked panel will show its content area.
JavaScript:
panelToggle = (panel) => {
if (panel.classList.contains("show")) {
hide(panel);
} else {
hideAll();
setTimeout (function() {
show(panel);
}, 100);
}
}
Adding The Event Listeners
Lastly, it is time to attach the panelToggle function to our event listener for each clickable panel. The argument passed for each panel is the variable I declared in JavaScript via querySelector. This argument is replacing the parameter of panel in all my previously declared functions and is how JavaScript knows which collapsable content area to manipulate on click.
HTML:
<div id="accordian">
<div class="clickable-panel">
<a href="#" id="panel1" onclick="panelToggle(panel1)">Panel 1</a>
</div>
<div class="collapse panel1">
<!-- collapsable content -->
</div>
<div class="clickable-panel">
<a href="#" id="panel2" onclick="panelToggle(panel2)">Panel 2</a>
</div>
<div class="collapse panel2">
<!-- collapsable content -->
</div>
<div class="clickable-panel">
<a href="#" id="panel3" onclick="panelToggle(panel3)">Panel 3</a>
</div>
<div class="collapse panel3">
<!-- collapsable content -->
</div>
<div class="clickable-panel">
<a href="#" id="panel4" onclick="panelToggle(panel4)">Panel 4</a>
</div>
<div class="collapse panel4">
<!-- collapsable content -->
</div>
</div>