Accordion
An accordion is like a digital folder that expands and collapses to reveal or hide its contents. It's a nifty tool for organizing information, allowing users to quickly navigate through a website without feeling overwhelmed by a barrage of text. With an accordion, users can easily find what they're looking for, without having to scroll through endless pages of content.
Example
<div class="accordion">
<div class="item">
<h4 class="question">
<button
type="button"
aria-expanded="true"
aria-controls="a1"
>
First Amendment
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m6 9 6 6 6-6"></path>
</svg>
</button>
</h4>
<div id="a1" class="answer">
<p>
Congress shall make no law respecting an establishment of religion, or
prohibiting the free exercise thereof; or abridging the freedom of speech,
or of the press; or the right of the people peaceably to assemble, and to
petition the Government for a redress of grievances.
</p>
</div>
</div>
<div class="item">
<h4 class="question">
<button
type="button"
aria-expanded="true"
aria-controls="a1"
>
First Amendment
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m6 9 6 6 6-6"></path>
</svg>
</button>
</h4>
<div id="a1" class="answer">
<p>
Congress shall make no law respecting an establishment of religion, or
prohibiting the free exercise thereof; or abridging the freedom of speech,
or of the press; or the right of the people peaceably to assemble, and to
petition the Government for a redress of grievances.
</p>
</div>
</div>
</div>
@use '~/styles/app.scss' as app;
$accordion: app.get-theme(app.$theme, 'components.accordion');
@include app.generate-component(
$accordion,
'accordion',
app.$config,
app.$theme
);
const accordion = (elements, options) => {
const defaults = {
accordion_item : ':scope > .item',
question_class : ':scope > .question',
answer_class : ':scope > .answer',
toggle_element : ':scope > .question > button',
show_multiply : false,
a11y : true
};
// Options
const settings = Object.assign({}, defaults, options);
// @TODO Add transitions
// @Add keycodes
const _a11y_checker = () => {
// @TODO Add aria-expanded check
// @TODO Add IDs
}
const _add_event_listeners = (question, answer, toggle_element) => {
toggle_element.addEventListener('click', (e) => {
e.preventDefault();
question.classList.toggle('open');
answer.toggleAttribute('hidden');
if(toggle_element.getAttribute('aria-expanded') !== 'true') {
toggle_element.setAttribute('aria-expanded', 'true');
} else {
toggle_element.setAttribute('aria-expanded', 'false');
}
})
}
const _make_ID = () => {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(
/[xy]/g, (c) => {
let r = (Math.random() * 16) | 0;
let v = c === 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
},
);
}
const _init = () => {
// attach classes to buttons and containers
[].forEach.call(elements, (accordion) => {
let accordion_items = accordion.querySelectorAll(settings.accordion_item);
if(accordion_items.length > 0) {
[].forEach.call(accordion_items, (item) => {
_add_event_listeners(
item.querySelector(settings.question_class),
item.querySelector(settings.answer_class),
item.querySelector(settings.toggle_element)
);
})
}
});
}
// Initiating the accordion
if(accordion) {
_init();
}
return ({
elements : elements,
open : () => {}
})
}
const accords = accordion(document.querySelectorAll('.accordion'), {
});
console.log(accords);
Accordion with Schema
<div class="accordion" itemscope itemtype="https://schema.org/FAQPage">
<div class="item" itemprop="mainEntity" itemscope itemtype="https://schema.org/Question">
<h4 class="question open">
<button
id="question1"
type="button"
aria-expanded="true"
aria-controls="answer1"
itemprop="name"
>
First Amendment
<svg class="icon" width="24px" height="24px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7 14.5L12 9.5L17 14.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</h4>
<div id="answer1" class="answer" itemprop="acceptedAnswer" itemscope
itemtype="https://schema.org/Answer" role="region" aria-labelledby="question1">
<p itemprop="text">
Congress shall make no law respecting an establishment of religion, or
prohibiting the free exercise thereof; or abridging the freedom of speech,
or of the press; or the right of the people peaceably to assemble, and to
petition the Government for a redress of grievances.
</p>
</div>
</div>
<div class="item" itemprop="mainEntity" itemscope itemtype="https://schema.org/Question">
<h4 class="question">
<button
id="question2"
type="button"
aria-expanded="true"
aria-controls="answer2"
itemprop="name"
>
First Amendment
<svg class="icon" width="24px" height="24px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7 14.5L12 9.5L17 14.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</h4>
<div id="answer2" class="answer" itemprop="acceptedAnswer" itemscope
itemtype="https://schema.org/Answer" role="region" aria-labelledby="question2" hidden>
<p itemprop="text">
Congress shall make no law respecting an establishment of religion, or
prohibiting the free exercise thereof; or abridging the freedom of speech,
or of the press; or the right of the people peaceably to assemble, and to
petition the Government for a redress of grievances.
</p>
</div>
</div>
</div>
@use '~/styles/app.scss' as app;
$accordion: app.get-theme(app.$theme, 'components.accordion');
@include app.generate-component(
$accordion,
'accordion',
app.$config,
app.$theme
);
const accordion = (elements, options) => {
const defaults = {
accordion_item : ':scope > .item',
question_class : ':scope > .question',
answer_class : ':scope > .answer',
toggle_element : ':scope > .question > button',
show_multiply : false,
a11y : true
};
// Options
const settings = Object.assign({}, defaults, options);
// @TODO Add transitions
// @Add keycodes
const _a11y_checker = () => {
// @TODO Add aria-expanded check
// @TODO Add IDs
}
const _add_event_listeners = (question, answer, toggle_element) => {
toggle_element.addEventListener('click', (e) => {
e.preventDefault();
question.classList.toggle('open');
answer.toggleAttribute('hidden');
if(toggle_element.getAttribute('aria-expanded') !== 'true') {
toggle_element.setAttribute('aria-expanded', 'true');
} else {
toggle_element.setAttribute('aria-expanded', 'false');
}
})
}
const _make_ID = () => {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(
/[xy]/g, (c) => {
let r = (Math.random() * 16) | 0;
let v = c === 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
},
);
}
const _init = () => {
// attach classes to buttons and containers
[].forEach.call(elements, (accordion) => {
let accordion_items = accordion.querySelectorAll(settings.accordion_item);
if(accordion_items.length > 0) {
[].forEach.call(accordion_items, (item) => {
_add_event_listeners(
item.querySelector(settings.question_class),
item.querySelector(settings.answer_class),
item.querySelector(settings.toggle_element)
);
})
}
});
}
// Initiating the accordion
if(accordion) {
_init();
}
return ({
elements : elements,
open : () => {}
})
}
const accords = accordion(document.querySelectorAll('.accordion'), {
});
console.log(accords);