<div id="cwf-accordion--0" class="cwf-accordion">
<button class="cwf-accordion__toggle" aria-controls="cwf-accordion--0" data-action="true">
Expand All
</button>
<div class="cwf-accordion__wrapper">
<div id="cwf-accordion__panel--0" class="cwf-accordion__panel" role="dialog" aria-labelledby="cwf-accordion__title--0" aria-describedby="cwf-accordion__body--0">
<div class="cwf-accordion__heading" tabindex="0" aria-controls="cwf-accordion__overflow--0" aria-expanded="true">
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="chevron-up" class="svg-inline--fa fa-chevron-up fa-w-14 cwf-accordion__chevron" role="presenation" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<path fill="currentColor" d="M240.971 130.524l194.343 194.343c9.373 9.373 9.373 24.569 0 33.941l-22.667 22.667c-9.357 9.357-24.522 9.375-33.901.04L224 227.495 69.255 381.516c-9.379 9.335-24.544 9.317-33.901-.04l-22.667-22.667c-9.373-9.373-9.373-24.569 0-33.941L207.03 130.525c9.372-9.373 24.568-9.373 33.941-.001z" />
</svg>
<strong id="cwf-accordion__title--0" class="cwf-accordion__title">
Panel Title 1
</strong>
</div>
<div id="cwf-accordion__overflow--0" class="cwf-accordion__overflow">
<div id="cwf-accordion__body--0" class="cwf-accordion__body">
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed imperdiet risus suscipit sapien congue pretium. In hac habitasse platea dictumst. Donec vel nisi quis dui aliquam porta. Sed nec dolor ullamcorper velit suscipit elementum. Suspendisse et augue vitae tellus congue mollis. Integer sem ex, rhoncus eget cursus non, rhoncus eget ipsum. Praesent ullamcorper facilisis diam, sit amet commodo sem auctor egestas. Vivamus vestibulum, ligula vitae molestie finibus, est lacus ornare nisl, quis ultrices massa mi vitae augue. Pellentesque nulla lectus, placerat id tellus ac, fringilla aliquam velit. Aliquam ullamcorper consectetur urna, vitae maximus nunc malesuada eget. Vestibulum lobortis ut quam a congue. Phasellus facilisis erat at feugiat faucibus. Curabitur cursus dolor in laoreet sodales.
</p>
</div>
</div>
</div>
<div id="cwf-accordion__panel--1" class="cwf-accordion__panel" role="dialog" aria-labelledby="cwf-accordion__title--1" aria-describedby="cwf-accordion__body--1">
<div class="cwf-accordion__heading" tabindex="0" aria-controls="cwf-accordion__overflow--1" aria-expanded="false">
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="chevron-up" class="svg-inline--fa fa-chevron-up fa-w-14 cwf-accordion__chevron" role="presenation" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<path fill="currentColor" d="M240.971 130.524l194.343 194.343c9.373 9.373 9.373 24.569 0 33.941l-22.667 22.667c-9.357 9.357-24.522 9.375-33.901.04L224 227.495 69.255 381.516c-9.379 9.335-24.544 9.317-33.901-.04l-22.667-22.667c-9.373-9.373-9.373-24.569 0-33.941L207.03 130.525c9.372-9.373 24.568-9.373 33.941-.001z" />
</svg>
<strong id="cwf-accordion__title--1" class="cwf-accordion__title">
Panel Title 2
</strong>
</div>
<div id="cwf-accordion__overflow--1" class="cwf-accordion__overflow">
<div id="cwf-accordion__body--1" class="cwf-accordion__body">
<p>
Nulla efficitur dui risus, et venenatis lacus commodo a. Nullam leo odio, posuere vitae erat eu, iaculis vulputate magna. Donec posuere elit eget lectus fermentum euismod. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam blandit augue ut nulla dictum, placerat vehicula mi pulvinar. Maecenas in quam est. Integer nec ultricies purus, malesuada accumsan neque. Maecenas mauris ligula, lobortis et congue nec, condimentum eget eros. Vivamus pharetra rutrum tellus, sit amet molestie lorem pellentesque et. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nullam pretium nibh ac est dapibus porta eu sollicitudin odio. Nunc sed risus faucibus, commodo eros iaculis, volutpat diam. Mauris mattis velit augue, id tincidunt leo rhoncus non.
</p>
</div>
</div>
</div>
<div id="cwf-accordion__panel--2" class="cwf-accordion__panel" role="dialog" aria-labelledby="cwf-accordion__title--2" aria-describedby="cwf-accordion__body--2">
<div class="cwf-accordion__heading" tabindex="0" aria-controls="cwf-accordion__overflow--2" aria-expanded="false">
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="chevron-up" class="svg-inline--fa fa-chevron-up fa-w-14 cwf-accordion__chevron" role="presenation" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<path fill="currentColor" d="M240.971 130.524l194.343 194.343c9.373 9.373 9.373 24.569 0 33.941l-22.667 22.667c-9.357 9.357-24.522 9.375-33.901.04L224 227.495 69.255 381.516c-9.379 9.335-24.544 9.317-33.901-.04l-22.667-22.667c-9.373-9.373-9.373-24.569 0-33.941L207.03 130.525c9.372-9.373 24.568-9.373 33.941-.001z" />
</svg>
<strong id="cwf-accordion__title--2" class="cwf-accordion__title">
Panel Title 3
</strong>
</div>
<div id="cwf-accordion__overflow--2" class="cwf-accordion__overflow">
<div id="cwf-accordion__body--2" class="cwf-accordion__body">
<p>
Proin ex enim, elementum vitae ipsum vel, congue molestie mauris. Vestibulum consequat scelerisque commodo. In malesuada tincidunt finibus. Donec efficitur, quam et interdum aliquet, nisl sapien sagittis ex, nec luctus est risus eget mauris. Etiam porttitor, magna at sollicitudin facilisis, metus ipsum commodo arcu, vitae tristique lorem quam eget neque. Aliquam rhoncus bibendum est, vel accumsan leo molestie efficitur. Sed nec pretium elit, a vestibulum magna.
</p>
</div>
</div>
</div>
</div>
</div>
<div id="cwf-accordion--{{ group[0].id }}" class="cwf-accordion">
{% if group.length > 1 -%}
<button class="cwf-accordion__toggle"
aria-controls="cwf-accordion--{{ group[0].id }}"
data-action="true">
Expand All
</button>
{% endif -%}
<div class="cwf-accordion__wrapper">
{% for accordion in group -%}
<div id="cwf-accordion__panel--{{ accordion.id }}"
class="cwf-accordion__panel"
role="dialog"
aria-labelledby="cwf-accordion__title--{{ accordion.id }}"
aria-describedby="cwf-accordion__body--{{ accordion.id }}">
<div class="cwf-accordion__heading"
tabindex="0"
aria-controls="cwf-accordion__overflow--{{ accordion.id }}"
aria-expanded="{{ accordion.openByDefault }}">
{% include '../../shared/fontawesome/chevron-up-solid.svg' with {
class: 'cwf-accordion__chevron',
role: 'presenation'
} %}
<strong id="cwf-accordion__title--{{ accordion.id }}"
class="cwf-accordion__title">
{{ accordion.title }}
</strong>
</div>
<div id="cwf-accordion__overflow--{{ accordion.id }}"
class="cwf-accordion__overflow">
<div id="cwf-accordion__body--{{ accordion.id }}"
class="cwf-accordion__body">
<p>
{{ accordion.body }}
</p>
</div>
</div>
</div>
{% endfor -%}
</div>
</div>
{
"group": [
{
"id": 0,
"openByDefault": true,
"title": "Panel Title 1",
"body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed imperdiet risus suscipit sapien congue pretium. In hac habitasse platea dictumst. Donec vel nisi quis dui aliquam porta. Sed nec dolor ullamcorper velit suscipit elementum. Suspendisse et augue vitae tellus congue mollis. Integer sem ex, rhoncus eget cursus non, rhoncus eget ipsum. Praesent ullamcorper facilisis diam, sit amet commodo sem auctor egestas. Vivamus vestibulum, ligula vitae molestie finibus, est lacus ornare nisl, quis ultrices massa mi vitae augue. Pellentesque nulla lectus, placerat id tellus ac, fringilla aliquam velit. Aliquam ullamcorper consectetur urna, vitae maximus nunc malesuada eget. Vestibulum lobortis ut quam a congue. Phasellus facilisis erat at feugiat faucibus. Curabitur cursus dolor in laoreet sodales. "
},
{
"id": 1,
"openByDefault": false,
"title": "Panel Title 2",
"body": "Nulla efficitur dui risus, et venenatis lacus commodo a. Nullam leo odio, posuere vitae erat eu, iaculis vulputate magna. Donec posuere elit eget lectus fermentum euismod. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam blandit augue ut nulla dictum, placerat vehicula mi pulvinar. Maecenas in quam est. Integer nec ultricies purus, malesuada accumsan neque. Maecenas mauris ligula, lobortis et congue nec, condimentum eget eros. Vivamus pharetra rutrum tellus, sit amet molestie lorem pellentesque et. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nullam pretium nibh ac est dapibus porta eu sollicitudin odio. Nunc sed risus faucibus, commodo eros iaculis, volutpat diam. Mauris mattis velit augue, id tincidunt leo rhoncus non. "
},
{
"id": 2,
"openByDefault": false,
"title": "Panel Title 3",
"body": "Proin ex enim, elementum vitae ipsum vel, congue molestie mauris. Vestibulum consequat scelerisque commodo. In malesuada tincidunt finibus. Donec efficitur, quam et interdum aliquet, nisl sapien sagittis ex, nec luctus est risus eget mauris. Etiam porttitor, magna at sollicitudin facilisis, metus ipsum commodo arcu, vitae tristique lorem quam eget neque. Aliquam rhoncus bibendum est, vel accumsan leo molestie efficitur. Sed nec pretium elit, a vestibulum magna."
}
]
}
// Accordion component styles
@import "../../shared/scss/animation";
@import "../../shared/scss/media";
@import "../../shared/scss/style";
.cwf-accordion {
display: flex;
flex-direction: column;
align-items: flex-end;
margin-bottom: 1rem;
--cwf-accordion--foreground-light-color: #{style__color(gray-light)};
--cwf-accordion--foreground-medium-color: #{style__color(gray-dark)};
--cwf-accordion--foreground-dark-color: #{style__color(black)};
--cwf-accordion--background-light-color: #{style__color(white-dark)};
--cwf-accordion--background-medium-color: #{darken(
style__color(white-dark),
5%
)};
--cwf-accordion--background-dark-color: #{style__color(white-darkest)};
}
.cwf-accordion__toggle {
margin-bottom: 0.5rem;
padding: 0;
border: none;
background-color: transparent;
font-size: 1rem;
text-decoration: underline;
color: var(--cwf-accordion--foreground-light-color);
@include animation__transition(color);
&:hover {
cursor: pointer;
}
&:hover,
&:focus {
color: var(--cwf-accordion--foreground-medium-color);
}
}
.cwf-accordion__wrapper {
width: 100%;
margin-bottom: -1px;
overflow: hidden;
@include style__shadow;
}
.cwf-accordion__panel {
border-top: 1px solid var(--cwf-accordion--background-medium-color);
&:first-child {
border-top: none;
}
&:last-child {
margin-bottom: 0;
}
}
.cwf-accordion__heading {
display: flex;
align-items: center;
margin-bottom: -1px;
padding: 0.75rem 1.5rem;
border-bottom: 1px solid var(--cwf-accordion--background-dark-color);
background-color: var(--cwf-accordion--background-light-color);
color: var(--cwf-accordion--foreground-medium-color);
@include animation__transition(background-color, color);
&:hover {
cursor: pointer;
}
&:hover,
&:focus {
background-color: var(--cwf-accordion--background-medium-color);
color: var(--cwf-accordion--foreground-dark-color);
}
}
.cwf-accordion__heading[aria-expanded="false"] {
color: var(--cwf-accordion--foreground-light-color);
&:hover,
&:focus {
color: var(--cwf-accordion--foreground-medium-color);
}
}
.cwf-accordion__chevron {
min-width: 1rem;
width: 1rem;
margin-right: 1.5rem;
@include animation__transition(color, transform);
}
.cwf-accordion__heading[aria-expanded="false"] .cwf-accordion__chevron,
.cwf-accordion__heading--closing .cwf-accordion__chevron {
transform: rotate(180deg);
}
.cwf-accordion__title {
font-size: 1rem;
@include animation__transition(color);
}
.cwf-accordion__overflow {
display: block;
overflow: hidden;
@include animation__transition(height);
}
.cwf-accordion__heading[aria-expanded="false"] ~ .cwf-accordion__overflow {
display: none;
}
.cwf-accordion__body {
padding: 1.5rem;
& > *:first-child {
margin-top: 0 !important;
padding-top: 0 !important;
}
& > *:last-child {
margin-bottom: 0 !important;
padding-bottom: 0 !important;
}
}
// Provide functionality to all accordions
class Accordion {
constructor({
accordion = 'cwf-accordion',
toggle = 'cwf-accordion__toggle',
panel = 'cwf-accordion__panel',
heading = 'cwf-accordion__heading',
closing = 'cwf-accordion__heading--closing',
overflow = 'cwf-accordion__overflow',
body = 'cwf-accordion__body'
} = {}) {
// Store all selectors
this.selectors = {
accordion,
toggle,
panel,
heading,
closing,
overflow,
body
};
// Initialize arrays for the toggles, accordions, and panels...
this.toggles = [];
this.accordions = [];
this.panels = [];
// ... as well as an object for the current event
this.current = {};
// Bind "this" to the necessary methods
this.onClick = this.onClick.bind(this);
this.onKeyDown = this.onKeyDown.bind(this);
this.onHashChange = this.onHashChange.bind(this);
}
// Get references to all accordion elements
getReferences() {
// Attempt to grab all toggles,
this.toggles = Array.from(
document.querySelectorAll(`.${this.selectors.toggle}`)
);
// ... accordions,...
this.accordions = Array.from(
document.querySelectorAll(`.${this.selectors.accordion}`)
);
// .. and accordion panels from the page
this.panels = [];
this.accordions.forEach((accordion) => {
const panels = Array.from(
accordion.querySelectorAll(`.${this.selectors.panel}`)
);
panels.forEach((panel) => {
const heading = panel.querySelector(
`.${this.selectors.heading}`
),
overflow = panel.querySelector(
`.${this.selectors.overflow}`
),
body = panel.querySelector(`.${this.selectors.body}`);
this.panels.push({
accordion,
panel,
heading,
overflow,
body
});
});
});
}
// Check whether reduced motion is enabled locally or globally
motionIsReduced() {
// Check if reduced motion is enabled/disabled locally...
const local = document.documentElement.getAttribute('data-reduced-motion'),
// ... or enabled globally
global = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
// If it's enabled/disabled locally, return its value...
if (local) return local === 'true';
// ... otherwise, return the global preference
return global;
}
// Calculate an element's height (including padding, margin, and border)
calcElHeight(element) {
// Get the computed styles and top/bottom margins of the element...
const styles = window.getComputedStyle(element),
margins =
parseFloat(styles.marginTop) + parseFloat(styles.marginBottom);
// ... and return their sum rounded up in pixels
return Math.ceil(element.offsetHeight + margins) + 'px';
}
// Open an accordion panel
openPanel({ heading, overflow, body }) {
// Focus the heading of the panel if instructed to do so
if (this.current.focus) heading.focus();
// If the panel is already opened, do nothing else
if (heading.getAttribute('aria-expanded') === 'true') return;
// If the current type is "hash", or if reduced motion is enabled locally or globally,...
if (this.current.type === 'hash' || this.motionIsReduced())
// ... simply open the panel with no animation
return heading.setAttribute('aria-expanded', true);
// Set the overflow's height to zero
overflow.style.height = 0;
// End the height transition
function transitionEnd({ propertyName }) {
// If the transition property isn't height, do nothing
if (propertyName !== 'height') return;
// Unbind from the overflow's transitionend event...
overflow.removeEventListener('transitionend', transitionEnd);
// ... and remove its height property
overflow.style.removeProperty('height');
}
// Start the height transition
function transitionStart() {
// Bind to the overflow's transitionend event...
overflow.addEventListener('transitionend', transitionEnd);
// ... and set its height to that of the body's
window.requestAnimationFrame(
() => (overflow.style.height = this.calcElHeight(body))
);
}
// Set the heading's aria-expanded attribute to true...
heading.setAttribute('aria-expanded', true);
// ... and start the height transition
window.requestAnimationFrame(transitionStart.bind(this));
}
// Close an accordion panel
closePanel({ heading, overflow, body }) {
// If the panel is already closed, do nothing else
if (heading.getAttribute('aria-expanded') === 'false') return;
// If the current type is "hash", or if reduced motion is enabled locally or globally,...
if (this.current.type === 'hash' || this.motionIsReduced())
// ... simply close the panel with no animation
return heading.setAttribute('aria-expanded', false);
// Grab the closing class from the global selectors
const { closing } = this.selectors;
// Set the overflow's height to that of the body's
overflow.style.height = this.calcElHeight(body);
// End the height transition
function transitionEnd({ propertyName }) {
// If the transition property isn't height, do nothing
if (propertyName !== 'height') return;
// Unbind from the overflow's transitionend event,...
overflow.removeEventListener('transitionend', transitionEnd);
// ... set the heading's aria-expanded to false,...
heading.setAttribute('aria-expanded', false);
// ... remove the closing class from the heading,...
heading.classList.remove(closing);
// ... and remove the overflow's height property
overflow.style.removeProperty('height');
}
// Start the height transition
function transitionStart() {
// Add a class to the heading signifying the panel is closing,...
heading.classList.add(closing);
// ... bind to the overflow's transitionend event,...
overflow.addEventListener('transitionend', transitionEnd);
// ... and set its height to zero
window.requestAnimationFrame(() => (overflow.style.height = 0));
}
// Start the height transition
window.requestAnimationFrame(transitionStart.bind(this));
}
// Set the the toggle button's action (expand or collapse)
setToggle(action) {
// Attempt to get the toggle to update, either from the current state or accordion...
const toggle =
this.current.toggle ||
this.toggles.find(
(toggle) =>
toggle.getAttribute('aria-controls') ===
this.current.accordion.id
);
// ... and if it wasn't found, do nothing else
if (!toggle) return;
// Finally, set the toggle's text...
toggle.textContent = action ? 'Expand All' : 'Collapse All';
// ... and action data attribute
toggle.setAttribute('data-action', action);
}
// Toggle the panels
togglePanels() {
// For each panel of the current accordion,...
this.current.panels.forEach(({ panel, heading, overflow, body }) => {
// Ensure the heading does not have a closing class...
heading.classList.remove(this.selectors.closing);
// ... and the overflow does not have a height set,...
overflow.style.removeProperty('height');
// ... and store all relevant panel elements
const target = { heading, overflow, body };
// If the type of action was not from a toggle and the panel does not equal the current panel, close it
if (this.current.type !== 'toggle' && panel !== this.current.panel)
return this.closePanel(target);
// If the current action is to open, open the panel
if (this.current.action) return this.openPanel(target);
// Otherwise, close the panel
return this.closePanel(target);
});
// Set the toggle (flip if the action was from a toggle, otherwise always open)
this.setToggle(
this.current.type === 'toggle' ? !this.current.action : true
);
// Finally, reset the current accordion
this.current = {};
}
// Find an element by type
findElementByType(type, element, target) {
// Define ways of finding an element based on type...
const instructions = {
toggle: {
accordion: document.getElementById(
target.getAttribute('aria-controls')
)
},
heading: {
accordion: target.parentNode.parentNode.parentNode,
panel: target.parentNode
},
hash: {
accordion: target.parentNode.parentNode,
panel: target
}
};
// ... and return the proper element
return instructions[type][element];
}
// Set action by type and target
setActionByTypeAndTarget(type, target) {
// If the type is "toggle", return the target's action dataset attribute...
if (type === 'toggle') return target.dataset.action === 'true';
// ... otherwise, return the opposite of the target's aria-expanded attribute
return target.getAttribute('aria-expanded') !== 'true';
}
// Set the current accordion
setCurrentAccordion(type, target, focus = false) {
// Grab all necessary references and data...
const accordion = this.findElementByType(type, 'accordion', target),
panel =
type !== 'toggle'
? this.findElementByType(type, 'panel', target)
: undefined,
toggle = type === 'toggle' ? target : undefined,
panels = this.panels.filter(
(panel) => panel.accordion === accordion
),
action =
type !== 'hash'
? this.setActionByTypeAndTarget(type, target)
: true;
// ... and set the current accordion
this.current = {
type,
accordion,
panel,
panels,
toggle,
action,
focus
};
}
// Handle click events
onClick(event) {
// Grab the current target of the event
const { currentTarget } = event;
// If the current target is a toggle, find its associated accordion accordingly...
if (currentTarget.classList.contains(this.selectors.toggle))
this.setCurrentAccordion('toggle', currentTarget);
// ... and if the current target is a panel heading, find its associated accordion accordingly
if (currentTarget.classList.contains(this.selectors.heading))
this.setCurrentAccordion('heading', currentTarget);
// Finally, prevent the default behavior...
event.preventDefault();
// ... and toggle the panels
return this.togglePanels(event);
}
// Focus a panel heading
focusHeading({ key, currentTarget }) {
// Grab the first/last panel as well as the max index
const firstPanel = this.current.panels[0],
maxIndex = this.current.panels.length - 1,
lastPanel = this.current.panels[maxIndex];
// If the "Home" key was pressed, focus the first panel heading...
if (key === 'Home') return firstPanel.heading.focus();
// ... and if the "End" key was pressed, focus the last panel heading
if (key === 'End') return lastPanel.heading.focus();
// Get the currently focused panel and its index
const currentPanel = this.current.panels.find(
(panel) => panel.heading === currentTarget
),
currentIndex = this.current.panels.indexOf(currentPanel);
// If the up arrow key was pressed,...
if (key === 'ArrowUp') {
// ... focus the last panel heading if the first panel heading is focused,...
if (!currentIndex) return lastPanel.heading.focus();
// ... otherwise focus the previous panel's heading
return this.current.panels[currentIndex - 1].heading.focus();
}
// If the down arrow key was pressed,...
if (key === 'ArrowDown') {
// ... focus the first panel heading if the last panel is focused,...
if (currentIndex === maxIndex) return firstPanel.heading.focus();
// ... otherwise focus the next panel's heading
return this.current.panels[currentIndex + 1].heading.focus();
}
}
// Handle keydown events
onKeyDown(event) {
// Grab the target and key of the event and create a list of valid keys
const { key, currentTarget } = event;
// If a valid key has not been pressed, do nothing else
if (
!['Enter', ' ', 'ArrowUp', 'ArrowDown', 'Home', 'End'].includes(key)
)
return;
// Otherwise, find the heading's associated accordion,...
this.setCurrentAccordion('heading', currentTarget);
// ... prevent the default behavior
event.preventDefault();
switch (key) {
// When the "Enter" or space key is pressed...
case 'Enter':
case ' ':
// ... toggle the panels
return this.togglePanels(event);
// When the up or down arrow keys are pressed...
case 'ArrowUp':
case 'ArrowDown':
// ... focus a panel heading relative to the current target
return this.focusHeading({ key, currentTarget });
// When the "Home" or "End" keys are pressed...
case 'Home':
case 'End':
// ... focus their respective panel heading
return this.focusHeading({ key });
}
}
// Handle hash change events
onHashChange(event) {
// Grab the location hash...
const { hash } = window.location,
id = hash.substr(1),
// ... and see if there's a valid target with that ID
target = this.panels.find((element) => element.panel.id === id);
// If no target was matched, do nothing else
if (!target) return;
// Set the current accordion...
this.setCurrentAccordion('hash', target.panel, true);
// ... and toggle the panels
return this.togglePanels(event);
}
// Initialize the accordion panels
initialize() {
// First, get all references to the accordions, toggles, and panels
this.getReferences();
// If no accordions were found on the page, do nothing
if (!this.accordions.length) return;
// If no accordion panels were found on the page, do nothing
if (!this.panels.length) return;
// Bind to the toggles' click events if any exist
if (this.toggles.length)
this.toggles.forEach((toggle) =>
toggle.addEventListener('click', this.onClick)
);
// Finally, for each panel heading...
this.panels.forEach(({ heading }) => {
// ... and bind to its click/keydown events
heading.addEventListener('click', this.onClick);
heading.addEventListener('keydown', this.onKeyDown);
});
// Handle location hash panel toggling on page load...
this.onHashChange();
// ... and on every hash change after that
window.addEventListener('hashchange', this.onHashChange);
}
}
export default Accordion;
// Provide functionality to all accordions
import Accordion from './functionality';
function initializeAccordions() {
const accordions = new Accordion();
accordions.initialize();
}
export default initializeAccordions;
The accordion component is a group of collapsable panels that hide or reveal their content when its header is clicked. When an accordion panel heading is clicked, all other accordion panels that are expanded will collapse. The accordion can be used with a single collapsible panel or multiple. When more than one collapsible panel is used within an accordion, a toggle button is added, allowing users to expand/collapse all accordion panels at once.
Whether you are using an accordion with a single panel or multiple, ensure that all panels are wrapped in a div.cwf-accordion__wrapper
element. Each individual panel will have a div.cwf-accordion__panel
element.
If you are using this component with a group of accordions ensure you are rendering the “Expand All / Collapse All” toggle button with a class of .cwf-accordion__toggle
.
Each accordion panel heading is listening for click events or keydown events when focused.
The following events will trigger the panel to expand or collapse:
The following keydown events will change panel heading focus: