DEV Community

Cover image for Creating Accessible Accordions with HTML, CSS & JavaScript
Liz Laffitte
Liz Laffitte

Posted on

Creating Accessible Accordions with HTML, CSS & JavaScript

An accordion, in development and design, is a graphical control element that consists of vertically stacked headers and hidden internal content. When clicked, a header's previously collapsed/hidden content box will expand to show its content; often text, images, or other grouped information.

You've probably seen (or used) an accordion on a FAQ page, with the questions shown in the headers, and the answers to those questions hidden in the content boxes.

Accordions can help increase the user experience on web and application pages with lots of information. They allow developers to group all that information on one page, but only display the higher level headers/titles. Users can then glance over all the titles without being overwhelmed by the details. They can more easily find, and click on, the headers/titles that they are interested in, and access the greater detail of the content.

There are countless widgets, plugins and other code snippets that will auto-magically add an accordion to your website or app. But you can also build a simple accordion with only HTML, CSS and JavaScript.

Accordion HTML

<ul id="accordion">
  <li>
    <button aria-controls="content-1" aria-expanded="false" id="accordion-control-1">FAQ 1</button>
    <div class="acc-item-content" aria-hidden="true" id="content-1">
      <p>Answer 1!</p>
    </div>
  </li>
  <li>
    <button aria-controls="content-2" aria-expanded="false" id="accordion-control-2">FAQ 2</button>
    <div class="acc-item-content" aria-hidden="true" id="content-2">
      <p>Answer 2</p>
    </div>
  </li>
  <li>
    <button aria-controls="content-3" aria-expanded="false" id="accordion-control-3">FAQ 3</button>
    <div class="acc-item-content" aria-hidden="true" id="content-3">
      <p>Answer 3</p>
    </div>
  </li>
  <li>
    <button aria-controls="content-4" aria-expanded="false" id="accordion-control-4">FAQ 4 </button>
    <div class="acc-item-content" aria-hidden="true" id="content-4">
      <p>Answer 4</p>
    </div>
  </li>
  <li>
    <button aria-controls="content-5" aria-expanded="false" id="accordion-control-5">FAQ 5</button>
    <div class="acc-item-content" aria-hidden="true" id="content-5">
      <p>Answer 5</p>
    </div>
  </li>
</ul>
Enter fullscreen mode Exit fullscreen mode

For the HTML, our entire accordion is housed in an unordered list. Each list item holds a div with the inner content and a button that will toggle the div's visibility. In an effort to make the accordion more accessible, we have aria-expanded and aria-hidden attributes, as well as aria-controls attributes on the buttons that correspond with the ids of the acc-item-content divs. These attributes will help users using screen readers understand our accordion, and what is and is not visible when the buttons are clicked on.

I've also got my text in paragraph tags, which will be helpful if you have more than a few sentences in the content divs.

Hopefully you're using a loop somewhere to dynamically create each list item and its child elements.

Accordion CSS

ul {
  list-style: none;
}

#accordion button:focus {
  border-radius: 0px;
  outline: none;
}
#accordion button {
  outline: none;
  background-color: DarkSeaGreen;
  padding: 10px;
  border: none;
  border-bottom: 1px solid darkslategrey;
  color: white;
  width: 100%;
  text-align: left;
  font-size: 16px;
  border-radius: 0px;
}
#accordion li {
  border: 1px solid DarkSlateGray;
  border-bottom: none;
}
.acc-item:last-child {
  border-bottom: 1px solid DarkSlateGray;
}
#accordion button::after {
  content: "\002B";
  font-weight: 900;
  font-size: 22px;
  float: right;
}

#accordion {
  width: 80%;
  max-width: 800px;
  min-width: 275px;
  margin: auto;
}

Enter fullscreen mode Exit fullscreen mode

Most of the CSS is for...style. We add background colors, borders and pseudo content to visually indicate that this is an accordion, and that you should click if you want to see more.

Technically, the only rule set you need is this one:

.acc-item-content {
  padding: 0px 10px;
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.3s ease-out;
}
Enter fullscreen mode Exit fullscreen mode

It sets the height of the content divs to 0 (hiding them from view); and gives the max-height a transition style and speed. This will come in handy when we get to the JavaScript, where we'll change the max-height values for our divs when the buttons are clicked.

Accordion JavaScript

window.addEventListener("DOMContentLoaded", (event) => {
  let buttons = document.querySelectorAll("#accordion button");
  buttons.forEach((button) => {
    let content = button.nextElementSibling;
    button.addEventListener("click", (event) => {
      if (button.classList.contains("active")) {
        button.classList.remove("active");
        button.setAttribute("aria-expanded", false);
        content.style.maxHeight = null;
        content.setAttribute("aria-hidden", true);
      } else {
        button.classList.add("active");
        button.setAttribute("aria-expanded", true);
        content.style.maxHeight = content.scrollHeight + "px";
        content.setAttribute("aria-hidden", false);
      }
    });
  });
});
Enter fullscreen mode Exit fullscreen mode

In pseudo code:

When all the DOM content is loaded...

  Collect all the buttons that are child elements of the element 
  with the id #accordion...

  Loop through each of these buttons...
     Grab the button's sibling element and save it in a variable 
     called content AND

     Add an event listener to each button, so that when the 
     button is clicked...

       If the button has the class active...
           Remove "active" from its class list AND

           Set its aria-expanded attribute to false AND

           Set the content variable's max-height value to null AND

           Set the content variable's aria-hidden attribute to true.

       Otherwise, if the button doesn't have the class active...
            Add "active" to its class list AND

           Set its aria-expanded attribute to true AND

           Set the content variable's max-height value even 
           to the value of the content variable's scroll height 
           (the height of an element's content) AND

           Set the content variable's aria-hidden attribute to false.
Enter fullscreen mode Exit fullscreen mode

And that's it: an accessible, simple accordion made with only HTML, CSS and vanilla JavaScript!

Top comments (3)

Collapse
 
hollyw00d profile image
Matt Jennings • Edited

@lizlaffitte
I'll be using this code for a real world project. Thank you!

Also I included an enhancement that resizes the height of an open accordion item (which uses has a max-height value set via JS) to account for resizing the browser width. See my example Pen where I adapt your code (specifically the FAQ 1 tab when open and resizing the browser window width):
codepen.io/hollyw00d/pen/zYJEZQr

Collapse
 
trainingmontage profile image
Jeff Caldwell

Just wanted to stop and say thanks for posting this! Really helped with a project I'm working on.

Collapse
 
sonniydsgn profile image
Alexey Kalinin

Thank you!