Day 03: FAQ Accordion
Build an accordion where clicking a heading opens its panel.
JavaScript focus
- looping through buttons
- toggling classes
- aria-expanded
- hidden attribute or max-height class strategy
Nice extras
- only one panel open at a time
- keyboard support
- smooth open/close animation with CSS classes
MDN prep
Accordion
The forEach() method of Array instances executes a provided function once for each array element.
The read-only classList property of the Element interface contains a live DOMTokenList collection representing the class attribute of the element. This can then be used to manipulate the class list.
The getAttribute() method of the Element interface returns the value of a specified attribute on the element.
If the given attribute does not exist, the value returned will be null.
The setAttribute() method of the Element interface sets the value of an attribute on the specified element. If the attribute already exists, the value is updated; otherwise a new attribute is added with the specified name and value.
The HTMLElement property hidden reflects the value of the element's hidden attribute.
The HTML
<div class="accordion">
<div class="accordion_item">
<h3>
<button type="button" class="accordion_trigger button" aria-expanded="false">
<span>Array.prototype.forEach()</span>
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><path fill="none" stroke="" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M256 112v288M400 256H112"/></svg>
</button>
</h3>
<div class="accordion_panel" role="region">
<p>The forEach() method of Array instances executes a provided function once for each array element.</p>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach" target="_blank">MDN: Array.prototype.forEach()</a></p>
</div>
</div>
<div class="accordion_item">
<h3>
<button type="button" class="accordion_trigger button" aria-expanded="false">
<span>Element: classList property</span>
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><path fill="none" stroke="" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M256 112v288M400 256H112"/></svg>
</button>
</h3>
<div class="accordion_panel" role="region">
<p>The read-only classList property of the Element interface contains a live DOMTokenList collection representing the class attribute of the element. This can then be used to manipulate the class list.</p>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/classList" target="_blank">MDN: Element: classList property</a></p>
</div>
</div>
<div class="accordion_item">
<h3>
<button type="button" class="accordion_trigger button" aria-expanded="false">
<span>Element: getAttribute() method</span>
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><path fill="none" stroke="" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M256 112v288M400 256H112"/></svg>
</button>
</h3>
<div class="accordion_panel" role="region">
<p>The getAttribute() method of the Element interface returns the value of a specified attribute on the element.</p>
<p>If the given attribute does not exist, the value returned will be null.</p>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute" target="_blank">Element: getAttribute() method</a></p>
</div>
</div>
<div class="accordion_item">
<h3>
<button type="button" class="accordion_trigger button" aria-expanded="false">
<span>Element: setAttribute() method</span>
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><path fill="none" stroke="" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M256 112v288M400 256H112"/></svg>
</button>
</h3>
<div class="accordion_panel" role="region">
<p>The setAttribute() method of the Element interface sets the value of an attribute on the specified element.</p>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute" target="_blank">Element: setAttribute() method</a></p>
</div>
</div>
<div class="accordion_item">
<h3>
<button type="button" class="accordion_trigger button" aria-expanded="false">
<span>HTMLElement: hidden property</span>
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><path fill="none" stroke="" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M256 112v288M400 256H112"/></svg>
</button>
</h3>
<div class="accordion_panel" role="region">
<p>The HTMLElement property hidden reflects the value of the element's hidden attribute.</p>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/hidden" target="_blank">HTMLElement: hidden property</a></p>
</div>
</div>
</div>
The JavaScript
function initAccordion() {
const root = document.querySelector(".accordion");
if (!root) return;
const items = root.querySelectorAll(".accordion_item");
const triggers = root.querySelectorAll(".accordion_trigger");
// first accordion is active and aria-expanded=true
items[0].classList.toggle("active");
triggers[0].setAttribute("aria-expanded", "true");
items.forEach((item) => {
const trigger = item.querySelector(".accordion_trigger");
trigger.addEventListener("click", () => {
// check for an active class
const isActive = item.classList.contains("active");
// on click, remove any active class
items.forEach((itm) => {
itm.classList.remove("active");
itm.querySelector(".accordion_trigger").setAttribute("aria-expanded", "false");
});
// add the active class if not active
if (!isActive) {
item.classList.add("active");
trigger.setAttribute("aria-expanded", "true");
}
});
});
}
initAccordion();