How to make the accordion in Bricks accessible?

0 comments

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:

the structure panel of the accordion

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:

custom HTML tag input

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:

the block element that contains the accordion content

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.

the button ID and placement in the structure panel

Your attributes should look somewhat like this:

The attributes panel, with custom attributes attached

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.

FAQs

What are the WCAG guidelines?

They’re part of the accessibility guidelines for the web, published by the World Wide Web Consortium.

Why focus on accessibility?

Sometimes users who visit your site are either using keyboard or screen reader to skim the pages. We have to make it easy for them to obtain information when they land on our website. Some countries enforce the accessibility guidelines as a priority, so it’s important for us to ensure our site is compliant with these standards to avoid getting sued for this.

Why is the default Bricks accordion not accessible?

The Bricks team is small, so it’s challenging for the to implement all requested features at once. All we can do is wait or customize just like I did.

References

Tags:

Leave the first comment