How to Code an Accessible JavaScript Accordion Component

Date:


In this tutorial, we’ll be creating an accessible accordion block component using vanilla JavaScript, that allows a user to toggle collapsible content.

1. Creating the Layout (HTML)

Since we’re building a custom component, our focus will be on making it accessible so it can be operated by all users.

This is the HTML for our accordion layout:

1
  <section id="accordion" class="accordion">
2
    <div class="accordion-container">
3
      <div class="accordion-item">
4
        <button class="accordion-trigger" id="accordion-trigger-1" aria-expanded="false" aria-controls="accordion-content-1">
5
          <span class="accordion-title">
6
          </span>
7
          <span class="accordion-icon">
8
            &plus;
9
          </span>
10
        </button>
11
        <div class="accordion-content" id="accordion-content-1" role="region" aria-labelledby="accordion-trigger-1">
12
          <p></p>
13
        </div>
14
      </div>
15
      
16
      ...
17
    </div>
18
  </section>

Making Things Accessible

Let’s take a look at the attributes we use to aid accessibility:

  • aria-expanded: this attribute is used on collapsible elements to indicate if the element is expanded or not.
  • aria-controls: this attribute is used on an element that is responsible for displaying the contents of another element. The aria-controls value is the id of the element being controlled.
  • aria-labelledby: this provides an accessible name to an element. 
  • role: this attribute is used to assign a semantic meaning to an element. For this tutorial, we’re assigning our accordion content the role region.
Devtools Accessibility tree of the accordion component showing the aria-labelledby and role valuesDevtools Accessibility tree of the accordion component showing the aria-labelledby and role values
Devtools Accessibility tree of the accordion component showing the aria-labelledby and role values

2. Styling the Content (CSS)

We’ll use CSS to handle the transition timing of the elements we want to animate. In this case, that’s the accordion-content and accordion-icon. We’ll also set the styling for when the accordion-item is active and hide the accordion content by default.

1
.accordion-icon {
2
  transition: transform 0.5s;
3
}
4
5
.accordion-item.is-active .accordion-icon {
6
  transform: rotate(45deg);
7
}
8
9
.accordion-content {
10
  height: 0;
11
  overflow: hidden;
12
  transition: 0.5s;
13
}

This is the rest of the styling for the layout:

1
.accordion-container {
2
  width: 90%;
3
  max-width: 1240px;
4
  margin: 0 auto;
5
  border: 3px solid #e0e0e0;
6
  border-radius: 24px;
7
  overflow: hidden;
8
}
9
10
.accordion-item {
11
  width: 100%;
12
}
13
14
.accordion-trigger {
15
  width: 100%;
16
  display: block;
17
  background-color: rgb(250, 250, 250);
18
  color: rgb(0, 0, 0);
19
  padding: 24px;
20
  font-size: 20px;
21
  font-weight: 500;
22
  font-family: 'Inter', sans-serif;
23
  text-align: left;
24
  border: none;
25
  display: flex;
26
  gap: 16px;
27
  justify-content: space-between;
28
  cursor: pointer;
29
}
30
31
.accordion-item:not(:first-of-type) .accordion-trigger {
32
  border-top: 3px solid #eaeaea;
33
}
34
35
.accordion-content {
36
  height: 0;
37
  overflow: hidden;
38
  transition: 0.5s;
39
}
40
41
.accordion-content p {
42
  margin: 24px;
43
}

One thing to note is that we don’t use any padding or margins on our accordion-content element and instead we only apply a margin to the child element. This is because height:0 doesn’t affect the margins or padding of an element so we would still have visible space if we set any.

3. Handling the Accordion Functionality (JavaScript)

First, we’ll need to get all the accordion items:

1
const accordionItems = document.querySelectorAll(".accordion-item");

Click Event Listener

Next, we’ll define a function on page load to add a click event listener to all the accordion trigger buttons.

1
window.addEventListener("load", () => {
2
  accordionItems.forEach((accordion, index) => {
3
    const accordionTrigger = accordion.querySelector(".accordion-trigger");
4
    accordionTrigger.addEventListener("click", () => toggleAccordion(index));
5
  });
6
});

Since our .querySelectorAll() method returns a NodeList, we can use the .forEach() method to loop through each accordion item element.

We can then target each trigger button inside a specific accordion item using accordion.querySelector(). Building the component this way makes it more scalable as targeting the trigger or content element isn’t tied down to a specific id or classname and instead is based on the accordion-item container it’s present in.

Our Toggle Function

Now we’ve set up our event listener, we can define our toggleAccordion() function.

First we’ll want to target the current accordion item. It’s possible to do this by using e.target.parentNode but for this demo we’ll use the index instead, which the toggleFunction accepts as a parameter. 

1
const toggleAccordion = (index) => {
2
  const currentAccordion = accordionItems[index];
3
  currentAccordion.classList.toggle("is-active");
4
};

When the accordion trigger is clicked, we’ll toggle the is-active class on the corresponding accordion item.

This function will also handle updating the height of our accordion content and setting the aria-expanded value of the accordion trigger to false.

We’re setting the height in JavaScript to create a transition effect on the accordion content since there’s no way of knowing the exact height of the accordion content in CSS.

This is what our updated toggleAccordion() function looks like:

1
const toggleAccordion = (index) => {
2
  resetAccordions(index);
3
4
  const currentAccordion = accordionItems[index];
5
  currentAccordion.classList.toggle("is-active");
6
7
  const accordionContent = currentAccordion.querySelector(".accordion-content");
8
  const accordionTrigger = currentAccordion.querySelector(".accordion-trigger");
9
10
  if (currentAccordion.classList.contains("is-active")) {
11
    accordionContent.style.height = `${accordionContent.scrollHeight}px`;
12
    accordionTrigger.setAttribute("aria-expanded", "true");
13
  } else {
14
    accordionContent.style.height = 0;
15
    accordionTrigger.setAttribute("aria-expanded", "false");
16
  }
17
};

Once we’ve toggled the is-active class on the currentAccordion, we’ll use the presence of the is-active class to determine the aria attributes and styling of the accordion trigger and content.

Including a Reset Function

We can further expand on this build by including a reset function to make sure only one accordion is open at a time. Our reset function will loop through the accordionItems NodeList and remove the active state on each accordion item, except for the current one based on its index.

Let’s define the resetAccordions() function:

1
const resetAccordions = (targetIndex) => {
2
  accordionItems.forEach((accordion, index) => {
3
    const accordionContent = accordion.querySelector(".accordion-content");
4
    const accordionTrigger = accordion.querySelector(".accordion-trigger");
5
6
    if (targetIndex != index) {
7
      accordion.classList.remove("is-active");
8
      accordionContent.style.height = 0;
9
      accordionTrigger.setAttribute("aria-expanded", "false");
10
    }
11
  });
12
};

We loop through the accordionItem and target all elements where the item index is not equal to the targetIndex being passed into the resetAccordions() function.

Finally, we can update our toggleAccordion() function to include resetting the accordion block when one accordion item is clicked:

1
const toggleAccordion = (index) => {
2
  resetAccordions(index);
3
4
  const currentAccordion = accordionItems[index];
5
  currentAccordion.classList.toggle("is-active");
6
7
  const accordionContent = currentAccordion.querySelector(".accordion-content");
8
  const accordionTrigger = currentAccordion.querySelector(".accordion-trigger");
9
10
  if (currentAccordion.classList.contains("is-active")) {
11
    accordionContent.style.height = `${accordionContent.scrollHeight}px`;
12
    accordionTrigger.setAttribute("aria-expanded", "true");
13
  } else {
14
    accordionContent.style.height = 0;
15
    accordionTrigger.setAttribute("aria-expanded", "false");
16
  }
17
};

Conclusion

And with that, we’ve completely built a JavaScript accordion component! Well done. Let’s remind ourselves of the finished product:



Source link

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Share post:

Subscribe

spot_imgspot_img

Popular

More like this
Related

Podcasts For UX Designers — Smashing Magazine

Podcasts are a fantastic opportunity to get up...

How this Series A startup used Webflow to turn their website into their best marketing asset

In the past 5 years, legislators worldwide have...

Prioritizing What to Test and When

For most SEOs, testing isn’t a novelty idea anymore....