Creating Accessible Accordions with HTML, CSS & JavaScript 
Tags: javascript, html, css
Published: 2021-02-17 00:37:14

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.

{% codepen https://codepen.io/LizLaffitte/pen/jOVBzbY %}

## 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>
```

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;
}

```
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;
}
```
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);
      }
    });
  });
});
```
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.
```
And that's it: an accessible, simple accordion made with only HTML, CSS and vanilla JavaScript!