Skip to content

Instantly share code, notes, and snippets.

@nichoth
Forked from cferdinandi/accordion-group.css
Created June 6, 2025 19:19
Show Gist options
  • Save nichoth/5e6cebe043835cfe2b1d21613f4fa5d5 to your computer and use it in GitHub Desktop.
Save nichoth/5e6cebe043835cfe2b1d21613f4fa5d5 to your computer and use it in GitHub Desktop.
Watch this tutorial: https://youtu.be/2k-iYzWZu2w
accordion-group {
background-color: #f7f7f7;
border-radius: 0.25em;
display: block;
margin-block-end: 1.5em;
padding: 0.5em 1em;
width: 100%;
}
accordion-group [accordion-trigger] {
background: transparent;
border: 0;
cursor: pointer;
display: block;
font: inherit;
margin: 0;
padding: 0;
width: 100%;
text-align: start;
}
accordion-group [accordion-trigger][aria-expanded="true"]::after {
content: " -";
}
accordion-group [accordion-trigger][aria-expanded="false"]::after {
content: " +";
}
customElements.define('accordion-group', class extends HTMLElement {
/**
* The class constructor object
*/
constructor () {
// Gives element access to the parent class properties
super();
// Get headings
this.headings = this.querySelectorAll(this.getAttribute('headings'));
if (!this.headings.length) return;
// Define if exclusive accordion or not
this.exclusive = this.hasAttribute('exclusive');
// Setup the DOM
this.setup();
// Listen for events
this.addEventListener('click', this);
}
/**
* Handle events on the Web Component
* @param {Event} event The event object
*/
handleEvent (event) {
// Get the clicked trigger button
const btn = event.target.closest('[accordion-trigger]');
if (!btn) return;
// Should accordion be hidden or expanded?
const isHidden = btn.getAttribute('aria-expanded') === 'true';
// Toggle the accordion
this.toggleAccordion(btn, isHidden);
// If exclusive, close all other open accordions
if (!this.exclusive) return;
const triggers = this.querySelectorAll('[accordion-trigger][aria-expanded="true"]');
for (const trigger of triggers) {
if (trigger === btn) continue;
this.toggleAccordion(trigger, true);
}
}
/**
* Toggle the accordion pane
* @param {Element} btn The accordion trigger
* @param {Boolean} isHidden If true, the content be hidden
*/
toggleAccordion (btn, isHidden) {
// Get the associated content
const content = this.querySelector(`#${btn.getAttribute('aria-controls')}`);
// Show the content and update ARIA
btn.setAttribute('aria-expanded', isHidden ? 'false' : 'true');
content.toggleAttribute('hidden', isHidden);
}
/**
* Setup the DOM on initial load
*/
setup () {
for (const heading of this.headings) {
// Create toggle button
const btn = document.createElement('button');
btn.setAttribute('accordion-trigger', '');
btn.innerHTML = heading.innerHTML;
heading.innerHTML = '';
heading.append(btn);
// Get content and hide it
const content = heading.nextElementSibling;
content.setAttribute('hidden', '');
// Define an ID if one is missing
if (!content.id) {
content.id = `accordion-group_${crypto.randomUUID()}`;
}
// Add ARIA attributes
btn.setAttribute('aria-expanded', false);
btn.setAttribute('aria-controls', content.id);
}
}
});
<!DOCTYPE html>
<html>
<head>
<title>Accordions</title>
<style type="text/css">
body {
margin: 1em auto;
max-width: 30em;
width: 88%;
}
</style>
<link rel="stylesheet" type="text/css" href="accordion-group.css">
</head>
<body>
<h1>Accordions</h1>
<accordion-group headings="h2">
<h2>Yo, ho ho!</h2>
<div>Yo, ho ho and a bottle of rum!</div>
<h2>Ahoy, there!</h2>
<div>Ahoy there, matey!</div>
</accordion-group>
<accordion-group headings="h2" exclusive>
<h2>Yo, ho ho!</h2>
<div>Yo, ho ho and a bottle of rum!</div>
<h2>Ahoy, there!</h2>
<div>Ahoy there, matey!</div>
</accordion-group>
<script src="accordion-group.js"></script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment