<ul class="accordion l-section-full__item">
<li class="accordion__theme--2">
<h3 class="accordion__heading">
<button class="accordion__trigger js-accordion__trigger" aria-expanded="false" aria-controls="accordion--multiple--content--1">
<div class="accordion__heading-inner">
<div class="accordion__heading-col accordion__trigger-icon">
<img src="/assets/img/accordion-icon-2.svg">
</div>
<div class="accordion__heading-col accordion__trigger-text">Aims & objectives</div>
<div class="accordion__heading-col accordion__trigger-chevron">
<svg viewBox="0 0 15 20" class="a-icon__svg a-icon__svg--x2">
<use xlink:href="#icon__chevron"></use>
</svg>
</div>
</div>
</button>
</h3>
<div class="accordion__content" id="accordion--multiple--content--1">
<div class="accordion__content-inner">
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Animi culpa dolorum enim molestiae molestias nemo nulla quas sed, temporibus voluptatem!</p>
</div>
</div>
</li>
<li class="accordion__theme--3">
<h3 class="accordion__heading">
<button class="accordion__trigger js-accordion__trigger" aria-expanded="false" aria-controls="accordion--multiple--content--2">
<div class="accordion__heading-inner">
<div class="accordion__heading-col accordion__trigger-icon">
<img src="/assets/img/accordion-icon-3.svg">
</div>
<div class="accordion__heading-col accordion__trigger-text">Methods</div>
<div class="accordion__heading-col accordion__trigger-chevron">
<svg viewBox="0 0 15 20" class="a-icon__svg a-icon__svg--x2">
<use xlink:href="#icon__chevron"></use>
</svg>
</div>
</div>
</button>
</h3>
<div class="accordion__content" id="accordion--multiple--content--2">
<div class="accordion__content-inner">
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Animi culpa dolorum enim molestiae molestias nemo nulla quas sed, temporibus voluptatem!</p>
</div>
</div>
</li>
<li class="accordion__theme--4">
<h3 class="accordion__heading">
<button class="accordion__trigger js-accordion__trigger" aria-expanded="false" aria-controls="accordion--multiple--content--3">
<div class="accordion__heading-inner">
<div class="accordion__heading-col accordion__trigger-icon">
<img src="/assets/img/accordion-icon-4.svg">
</div>
<div class="accordion__heading-col accordion__trigger-text">Plicy relevance & dissemination</div>
<div class="accordion__heading-col accordion__trigger-chevron">
<svg viewBox="0 0 15 20" class="a-icon__svg a-icon__svg--x2">
<use xlink:href="#icon__chevron"></use>
</svg>
</div>
</div>
</button>
</h3>
<div class="accordion__content" id="accordion--multiple--content--3">
<div class="accordion__content-inner">
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Animi culpa dolorum enim molestiae molestias nemo nulla quas sed, temporibus voluptatem!</p>
</div>
</div>
</li>
</ul>
<ul class="accordion l-section-full__item">
{% for item in items %}
<li class="accordion__theme--{{item.theme}}">
<h3 class="accordion__heading">
<button class="accordion__trigger js-accordion__trigger"
aria-expanded="false"
aria-controls="accordion--multiple--content--{{ loop.index }}">
<div class="accordion__heading-inner">
<div class="accordion__heading-col accordion__trigger-icon">
<img src="/assets/img/accordion-icon-{{item.theme}}.svg">
</div>
<div class="accordion__heading-col accordion__trigger-text">{{ item.buttonText }}</div>
<div class="accordion__heading-col accordion__trigger-chevron">
{% render '@icon--icon__chevron' with {'class' : 'a-icon__svg--x2'} %}
</div>
</div>
</button>
</h3>
<div class="accordion__content"
id="accordion--multiple--content--{{ loop.index }}">
<div class="accordion__content-inner">
{{ item.content }}
</div>
</div>
</li>
{% endfor %}
</ul>
{
"items": [
{
"buttonText": "Aims & objectives",
"content": "<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Animi culpa dolorum enim molestiae molestias nemo nulla quas sed, temporibus voluptatem!</p>",
"theme": "2"
},
{
"buttonText": "Methods",
"content": "<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Animi culpa dolorum enim molestiae molestias nemo nulla quas sed, temporibus voluptatem!</p>",
"theme": "3"
},
{
"buttonText": "Plicy relevance & dissemination",
"content": "<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Animi culpa dolorum enim molestiae molestias nemo nulla quas sed, temporibus voluptatem!</p>",
"theme": "4"
}
]
}
.accordion {
margin: 0;
&__heading {
margin: 0;
font-size: rem(22);
color: setcolor(1);
font-weight: 400;
@include mappy-bp(small) {
font-size: rem(40);
}
&-inner {
display: grid;
grid-template-columns: repeat(12, 1fr);
grid-gap: 30px;
max-width: $width-max;
margin: 0 auto;
}
&-col {
display: flex;
align-items: center;
}
button {
color: inherit;
font-size: inherit;
}
}
>li, >.accordion__item {
margin:0;
list-style: none;
padding-left: 0;
}
dd {
padding: 0;
}
&__trigger {
background: none;
display: grid;
grid-template-columns: 1fr;
color: hsl(0deg 0% 13%);
padding-left:rem(20);
font-weight: 400;
font-size: 1rem;
margin: 0;
padding: $elem-pad-sm;
position: relative;
text-align: left;
width: 100%;
outline: none;
border: 0;
border-bottom: 5px solid setcolor('white');
padding:$elem-pad-sm $elem-pad;
// @include mappy-bp(small) {
// padding:$elem-pad-lrg $elem-pad $elem-pad $elem-pad;
// }
&-text {
grid-column: span 11;
@include mappy-bp(small) {
grid-column: span 10;
}
}
&-icon {
display: none;
@include mappy-bp(small) {
display: block;
}
}
&-chevron {
display: flex;
color:setcolor(2);
svg {
transition: transform .2s ease-in-out;
}
.a-icon__svg {
width: 22px;
height: 22px;
@include mappy-bp(small) {
width: 40px;
height: 40px;
}
}
//transform: rotate(90deg) ;
//color:setcolor(1);
}
// &::after {
// position: absolute;
// content: url("data:image/svg+xml,%3Csvg width='12' height='8' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 1.604C0 1.157.269.755.682.584a1.105 1.105 0 0 1 1.202.24L6 4.938 10.116.823a1.103 1.103 0 1 1 1.56 1.562L6.78 7.28a1.103 1.103 0 0 1-.78.323 1.103 1.103 0 0 1-.78-.323L.324 2.385A1.101 1.101 0 0 1 0 1.604Z' fill='%23121212'/%3E%3C/svg%3E");
// right: 1rem;
// top: 1rem;
// transition: transform .2s ease-in-out;
// }
&[aria-expanded=true] {
.accordion__trigger-chevron {
svg {
transform: rotate(90deg);
}
//color: #ffffff;
}
// &::after {
// transform: rotate(-180deg);
// }
}
// &:focus, &:hover, &[aria-expanded="true"] {
// color: #ffffff;
// background: setcolor(3);
// }
&:hover {
// color: #ffffff;
background: setcolor(9);
}
&:focus {
outline: 4px solid transparent;
}
}
&__content {
transition: all .5s cubic-bezier(.41,.97,.36,.78);
overflow: hidden;
border-bottom: 5px solid setcolor('white');
&-inner {
padding: $elem-pad;
max-width: $width-max;
margin: 0 auto;
& > :last-child {
margin-bottom: 0;
}
@include mappy-bp(max) {
padding:$elem-pad 0;
}
}
}
&--expanded {
// margin: rem(15) rem(15) rem(10) rem(20);
}
&--alt {
>li, >.accordion__item {
margin-bottom: $elem-pad;
}
// .accordion__heading {
// border-bottom: none;
// }
.accordion__trigger {
border: none;
//padding:$elem-pad 0;
// margin:0 $elem-pad;
// width: calc(100% - ($elem-pad * 2));
// &:hover {
// background-color: none;
// }
}
.accordion__content-inner {
padding-left: 0;
padding-right: 0;
margin:0 $elem-pad;
border-top:1px solid setcolor(1);
}
}
}
.property {
.accordion__content-inner {
border-top: 0;;
}
.accordion__content-inner {
padding-top: 5px;
}
}
.accordion__theme {
&--1 {
.accordion {
&__trigger {
background-color: $theme-color-2;
&:hover {
//background-color: setcolor(8);
}
}
&__content {
background-color: $theme-color-2--shade;
}
}
}
&--2 {
.accordion {
&__trigger {
background-color: $theme-color-3;
&:hover {
//background-color: setcolor(10);
}
}
&__content {
background-color: $theme-color-3--shade;
}
}
}
&--3 {
.accordion {
&__trigger {
background-color: $theme-color-4;
&:hover {
//background-color: setcolor(12);
}
}
&__content {
background-color: $theme-color-4--shade;
}
}
}
}
import Accordion from './accordion.js';
(function () {
if (!Accordion) { // eslint-disable-line no-undef
return;
}
const selected = document.querySelectorAll('.accordion');
for (let i = selected.length; i--;) {
new Accordion(selected[i]); // eslint-disable-line no-undef
}
}());
export default function (elem, options) {
const expandedContent = [];
/**
* Override default options with options param.
*/
options = (() => {
const defaults = {
expand: (button, content) => {
content.style.maxHeight = `${content.scrollHeight}px`;
},
collapse: (button, content) => {
content.style.maxHeight = 0;
},
transitionEnd: (e) => {
if (e.propertyName !== "max-height") {
return;
}
if (
!e.currentTarget.classList.contains("accordion--expanded")
) {
e.currentTarget.setAttribute("hidden", "hidden");
}
},
resizeEvent: (e, expandedContent) => {
for (let i = expandedContent.length; i--; ) {
options.expand(null, expandedContent[i]);
}
},
init: true,
buttonSelector: "button.js-accordion__trigger",
accordionExpandedClass: "accordion--expanded",
expandMultiple: false,
disableHash: false,
};
const keys = Object.keys(defaults);
options = options || {};
for (let i = keys.length; i--; ) {
options[keys[i]] = options[keys[i]] || defaults[keys[i]];
}
if (elem.classList.contains("accordion--multiple")) {
options.expandMultiple = true;
}
if (elem.classList.contains("accordion--nohash")) {
options.disableHash = true;
}
return options;
})();
const buttons = elem.querySelectorAll(options.buttonSelector);
/**
* Toggle aria-expanded attributes and trigger visibility Change function.
*
* @param {event} e The triggered event.
*/
const toggle = (e) => {
e.preventDefault();
const button = e.currentTarget;
if (!options.expandMultiple) {
closeAllExcept(button);
}
button.setAttribute(
"aria-expanded",
button.getAttribute("aria-expanded") === "true" ? "false" : "true"
);
setVisibility(button);
// If the accordion is being expanded, update the URL hash
if (button.getAttribute("aria-expanded") === "true") {
const newPath = `${window.location.pathname}#${button.getAttribute(
"aria-controls"
)}`;
// Set the userTriggered flag to true because this is a user action
// userTriggered = true;
// Change the URL hash
if (!options.disableHash) {
history.pushState(null, null, newPath);
}
}
};
/**
* Handle keyboard input: arrows, home & end.
*
* @param {event} e The triggered event.
*/
const keyDown = (e) => {
const keyCode = e.keyCode || e.which;
let current = (() => {
for (let i = buttons.length; i--; ) {
if (buttons[i] === e.currentTarget) {
return i;
}
}
})();
switch (keyCode) {
case 40: // arrow down
e.preventDefault();
if (current === buttons.length - 1) {
current = -1;
}
buttons[current + 1].focus();
break;
case 38: // arrow up
e.preventDefault();
if (current === 0) {
current = buttons.length;
}
buttons[current - 1].focus();
break;
case 36: // home
e.preventDefault();
buttons[0].focus();
break;
case 35: // end
e.preventDefault();
buttons[buttons.length - 1].focus();
break;
}
};
/**
* Add events to buttons.
* Listen for animationEnd on accordionContent.
*/
const addEvents = () => {
const onResize = (e) => {
options.resizeEvent(e, expandedContent);
};
for (let i = buttons.length; i--; ) {
const button = buttons[i];
button.addEventListener("click", toggle);
button.addEventListener("keydown", keyDown);
const accordionContent = elem.querySelector(
`#${button.getAttribute("aria-controls")}`
);
accordionContent.addEventListener(
"transitionend",
options.transitionEnd
);
if (options.resizeEvent) {
window.addEventListener("resize", onResize);
}
}
window.addEventListener("hashchange", hashEvent);
};
/**
* Hide or show the accordion content.
*
* @param {Object} button The accordion button.
* @param {boolean|false} isInitial True if this is the first run
* triggered by init().
*/
const setVisibility = (button, isInitial) => {
const accordionContent = elem.querySelector(
`#${button.getAttribute("aria-controls")}`
);
if (!accordionContent) {
return;
}
if (button.getAttribute("aria-expanded") === "true") {
accordionContent.classList.add(options.accordionExpandedClass);
accordionContent.setAttribute("aria-hidden", "false");
accordionContent.removeAttribute("hidden");
expandedContent.push(accordionContent);
options.expand(button, accordionContent);
} else {
accordionContent.classList.remove(options.accordionExpandedClass);
accordionContent.setAttribute("aria-hidden", "true");
if (isInitial) {
accordionContent.setAttribute("hidden", "hidden");
}
expandedContent.filter((content) => content !== accordionContent);
options.collapse(button, accordionContent);
}
};
/**
* Set all attributes and toggle visibility accordingly.
*/
const setInitial = () => {
for (let i = buttons.length; i--; ) {
setVisibility(buttons[i], true);
}
};
const close = (button) => {
button.setAttribute("aria-expanded", "false");
setVisibility(button);
};
/**
* Closes all accordion items.
*/
const closeAll = () => {
for (let i = buttons.length; i--; ) {
close(buttons[i]);
}
};
/**
* Closes all accordion items.
*/
const closeAllExcept = (button) => {
for (let i = buttons.length; i--; ) {
if (buttons[i] !== button) {
close(buttons[i]);
}
}
};
const open = (button) => {
button.setAttribute("aria-expanded", "true");
setVisibility(button);
};
/**
* Opens all accordion items.
*/
const openAll = () => {
for (let i = buttons.length; i--; ) {
open(buttons[i]);
}
};
/**
* Open the accordion-content related to the location hash.
*/
const hashEvent = () => {
const hash = window.location.hash.replace("#", "");
if (!hash) {
return;
}
try {
const trigger = elem.querySelector(`[aria-controls=${hash}]`);
if (trigger) {
// Close all accordions except the one we want to open if expandMultiple is false
if (!options.expandMultiple) {
closeAllExcept(trigger);
}
open(trigger);
// Instead of directly focusing, we wait for the transition to end,
const accordionContent = elem.querySelector(
`#${trigger.getAttribute("aria-controls")}`
);
accordionContent.addEventListener(
"transitionend",
function () {
trigger.focus();
},
{ once: true }
); // Use 'once' option to ensure this listener only fires one time.
}
} catch (e) {}
};
/**
* Enable accordion functionality.
*/
const init = () => {
setInitial();
addEvents();
hashEvent();
};
if (options.init !== false) {
init();
}
return { init, closeAll, openAll };
}
An accordion component is used to:
Alternatives to using an accordion component:
There are two types of accordions:
Adding the class ‘dropdown’ will add the following behaviour: when you click on an element that isn’t a dropdown, all dropdowns will be closed. This behaviour is used in the case of an application that doesn’t refresh when clicking a button or link.
Accordions with one single expandable item are displayed with a chevron on the right-hand side.
By default, the single expandable item is collapsed.
The single expandable items has a label that tells what content is in the item. Do not start the label with a verb that describes the collapsing or expanding. This is not necessary because the item is already in an accordion. For example, if the item is a list of opening hours deze week, don’t use a label “Show opening hours this week” but use “Opening hours this week” instead.
Accordions with multiple expandable items are displayed in a list of items with labels with a plus or a minus icon on the left-hand side of each item.
By default, all the expandable items are collapsed. Multiple items can be expanded at the same time.
The expandable items each have a label that tells what content is in the item. Do not start the label with a verb that describes the collapsing or expanding. This is not necessary because the item is already in an accordion. For example, if an item is about transportation by train, don’t use a label “Show transportation by train” but use “Transportation by train” instead.
Create a new accordion object by running:
new Accordion(element);
Where element contains a button with:
And a collapsible element with:
By default, the accordion will initiate automatically
and hide or show the content
according to the aria-expanded attribute.
If the hash in the URL contains a matching ID,
this element will also be expanded.
For instance:
<div class="accordion">
<h3>
<button aria-controls="accordion--content-ID"
aria-expanded="false"
class="accordion--button">
single accordion
</button>
</h3>
<div class="accordion--content" id="accordion--content-ID">
...
</div>
</div>
Option | Type | Default | Description |
---|---|---|---|
expand |
function |
function(button, content) |
Function triggered after the expanded class is added, the ‘hidden’ attribute is removed from the content and aria-hidden is set to false. |
collapse |
function |
function(button, content) |
Function triggered after the expanded class is removed and aria-hidden is set to true. By default, the ‘hidden’ attribute is set in the transitionEnd function |
transitionEnd |
function |
function(event) |
Triggered for each transitionEnd event, use this to add the ‘hidden’ attribute after the content has been transitioned out of view. |
resizeEvent |
function |
function(event, expandedContent) |
ExpandedContent is an array containing all expanded elements. Use this to trigger the ‘expand’ function on window.resize |
init |
Boolean |
true |
Set to false if you want to manually initiate the accordion object (object.init()) |
buttonSelector |
String |
'button.accordion--button' |
QuerySelector to identify the accordion trigger button |
accordionExpandedClass |
String |
'accordion--expanded' |
Determine which class is added to the expanded content. |
Function | Description |
---|---|
init() |
Manually initiate the accordion, this will expand or collapse all content according to the aria-expanded state |
closeAll() |
Close all collapsible content in this accordion |
openAll() |
Open all collapsible content in this accordion |