According to the WCAG guidelines, the accordion element in Bricks is not accessible by default. It’s missing essential attributes like aria-label and is not keyboard navigable (which means I can’t use my keyboard to focus on them).
The accordion was first introduced in Bricks 1.5 and is still not accessible to this date. We can’t blame the developers for sure as they have a lot on their plate.
Luckily for us, Bricks is super customizable. It provides us the ability to add our own HTML attributes to any element on the page.
Let’s get started.
Change default elements
With the nestable accordion added, let’s first change the default div that holds the heading and icon to an actual heading element. Then, change the Bricks’ heading element to a HTML button tag.
Your structure panel should like this:
To change the HTML tag of an element, select the HTML tag dropdown in the left panel, choose custom, and an enter the appropriate tag inside the input below.
Like so:
The why:
- Any sort of heading or textual tag (p, span) should encapsulate the button tag.
- The button can’t encapsulate the heading tag because semantically that is incorrect.
- The heading element should not encapsulate other elements other than the button itself, unless the element itself is inside the button (not an actual child element of the heading).
Spice it up with CSS
I’m not sure if you’re able to see it but the default HTML button has some styling added to it. Let’s remove that.
%root% {
background: var(--white);
border: none;
}
Code language: CSS (css)
The h2 tag on my accordion is too big so I’ll change that too.
%root% {
font-size: var(--h4);
}
Code language: CSS (css)
Configure accessible HTML attributes
To ensure that the HTML tags are indeed accessible, we need to add custom attributes to the corresponding elements.
To add HTML attributes to an element in Bricks, go to Style > Attributes. They have documentation for this.
Here are the list of attributes that I add for each element.
Button
- type=”button”
- aria-controls=”brxe-ffppwd” // change according to the ID
- aria-expanded=”false”
The aria-controls attribute
is different for every element. The value should reference the ID of the element that contains the hidden content of the accordion. In my case, it’s this div
block right here:
Heading
- role=”heading”
- aria-level=”3″ // change according to the heading tag
The aria-level refers to the level of the heading tag in the DOM. Ex: aria-level="2"
for a h2 tag.
Block panel
- Optional: aria-labelledby=”brxe-mzfjix” // change according to button ID
- Optional: role=”region”
The aria-labelledy for the block panel also works the same way as the button element, the only different being is to use the ID of the button elements that has the aria-controls attribute attached to it.
Your attributes should look somewhat like this:
Manipulating the aria-expanded
attribute
There are two ways — as far as I know — that we can use to change the aria-expanded
attribute of the accordion whenever any one of the panels are clicked.
Using Bricks’ filter hooks
Bricks introduced their JavaScript filter hooks just recently, back in version 1.9.8 (at the time of writing, the current version is 1.9.9).
They introduced two filters, which are the bricks/accordion/open
and bricks/accordion/close
.
We are going to use both of them to change the aria-expanded
attribute on the button (only the clicked one!) from false to true when the accordion is open and vice versa.
The code:
<script>
// Listen for the 'bricks/accordion/open' event
document.addEventListener('bricks/accordion/open', (event) => {
// Extract information from the event detail
const { elementId, openItem } = event.detail;
// Only target elementID
if( elementId !== 'qlrxob' ) {
return;
}
// Your custom logic here
openItem.querySelector('button').setAttribute( 'aria-expanded', openItem.classList.contains( 'brx-open' ) );
});
</script>
Code language: HTML, XML (xml)
<script>
// Listen for the 'bricks/accordion/close' event
document.addEventListener('bricks/accordion/close', (event) => {
// Extract information from the event detail
const { elementId, closeItem } = event.detail;
// Only target elementID
if( elementId !== 'qlrxob' ) {
return;
}
// Your custom logic here
closeItem.querySelector('button').setAttribute( 'aria-expanded', closeItem.classList.contains( 'brx-open' ) );
});
</script>
Code language: HTML, XML (xml)
Using the MutationObserver API
To be honest, before I searched the docs, I actually had my own solution to this😂. Although, I would recommend using the filters as they’re native to Bricks.
As quoted by MDN, the MutationObserver API allows us to watch for changes in the DOM.
With this API, we can look for class changes in the DOM and update the attribute for each change depending on whether the accordion is open or closed.
This is how I wrote the code:
// wait for the dom to load to avoid slow page speed
document.addEventListener( 'DOMContentLoaded', function() {
const options = {
attributes: true
}
const observer = new window.MutationObserver( ( mutationList, observer ) => {
// loop through each mutation and check if the class attribute has changed
mutationList.forEach( mutation => {
if ( mutation.type === 'attributes' && mutation.attributeName === 'class' ) {
let clickedAccordion = mutation.target;
// if the accordion is open, set aria-expanded to true, otherwise set it to false
clickedAccordion.querySelector( '.accordion__item-title-button' ).setAttribute( 'aria-expanded', clickedAccordion.classList.contains( 'brx-open' ) );
}
})
} );
// initialize an observer for each accordion item
const accordionList = document.querySelectorAll( '.accordion__item' );
accordionList.forEach( accordion => {
observer.observe( accordion, options );
} );
} );
Code language: JavaScript (javascript)
To sum up the code; for each accordion item, we attach it to an observer that’s listening for changes and if the mutation is a class, then get the target and update the aria-expanded
attribute to true.
Congrats, we now have an accessible nested accordion in Bricks.