Creating an Animated and Accessible Accordion with JavaScript, HTML, and CSS
Tags: javascript, html, css, frontend
Published: 2023-06-27 17:56:41

Accordions are a popular UI component used to display collapsible sections of content, allowing users to expand or collapse specific sections as needed. In this article, we will explore how to create an animated and accessible accordion using JavaScript, HTML, and CSS.


### Tech Stack
The following technologies were used to create the accordion widget:

- HTML
- CSS
- JavaScript

### Features
- Dynamically generates accordion sections based on data from a JSON file.
- Supports keyboard navigation for improved accessibility.
- Provides smooth animation effects when expanding and collapsing sections.
- Customizable styles to match your design preferences.

### Understanding Accessibility in Accordions
Accessibility is about designing and developing applications and websites that can be accessed and used by individuals with disabilities. When it comes to accordions, we need to consider a few key accessibility aspects:

- Keyboard Accessibility: Users should be able to navigate and interact with the accordion using only the keyboard. This is particularly important for users who cannot use a mouse.

- Screen Reader Compatibility: Screen reader users rely on assistive technologies to access and navigate web content. To ensure compatibility, we need to provide appropriate HTML structure and use ARIA (Accessible Rich Internet Applications) attributes to convey the accordion's state and behavior.

- Focus Management: Proper focus management is crucial to indicate the currently focused element and maintain logical tab order within the accordion.
Fore more information refer, [Accordion aria example](https://www.w3.org/WAI/ARIA/apg/patterns/accordion/examples/accordion/)

## Prerequisites:
To follow along with this tutorial, you should have a basic understanding of HTML, CSS, and JavaScript

## Some Important links:
- Repo Link:- https://github.com/Sumukha210/accordion
- [Check out Live Demo](https://sumukha210.github.io/accordion/src/index.html)
- Below javascript code is written using ES6 classes, if you want to see function based code, https://github.com/Sumukha210/accordion/blob/master/src/function_based.js
- [Animating height in css by Kevin Powel](https://youtu.be/B_n4YONte5A)
- Aria Accordion example:- https://www.w3.org/WAI/ARIA/apg/patterns/accordion/examples/accordion/

### Setting Up the HTML Structure:
Let's start by creating the HTML structure for our accordion. We'll have a main container element, and within it, we'll dynamically generate section elements using JavaScript.

```html
<!DOCTYPE html>
<html lang="en">

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Accordion</title>
        <link rel="stylesheet" href="./style.css">
    </head>

    <body>

        <header>
            <h1>Accordion</h1>
        </header>

        <main>



        </main>

        <script src="./app.js"></script>
    </body>

</html>
```
### Styling the Accordion:
Next, we need to define the CSS styles to make our accordion visually appealing. Please refer to the provided CSS file  style.css file for the complete styles. You can customize the styles based on your design preferences.
[style.css](https://github.com/Sumukha210/accordion/blob/master/src/style.css)

### JavaScript Implementation:
Now let's dive into the JavaScript code and understand how the accordion functionality is implemented.

```js
class Section {
  constructor(main) {
    this.main = main;
  }

  createSection(title, content, index) {
    const section = document.createElement("section");
    section.innerHTML = `
        <h3 class="heading">
          <button aria-expanded="false" tabindex="${index === 0 ? 0 : -1}" data-index="${index}">${title}</button>
        </h3> 
        <div class="content" role="region">
            <div><p>${content}</p></div>
        </div>
    `;

    this.main.appendChild(section);
  }
}



class Accordion extends Section {
  constructor() {
    const main = document.querySelector("main");
    super(main);

    this.main.addEventListener("click", this.handleMainClick);
    this.main.addEventListener("keydown", this.handleMainPress);
    this.getData();

  }

  async getData() {
    try {
      const data = await fetch("./data.json");
      const posts = await data.json();
      posts.forEach((post, index) => {
        this.createSection(post.title, post.content, index);
      });
    } catch (error) {
      console.log(error);
    }
  }

  handleMainPress = (e) => {
    if (e.code === "Tab") {
      const nextNumber = parseInt(e.target.dataset.index) + 1;
      this.setButtonTabIndex(nextNumber < this.main.childElementCount ? nextNumber : 0);
    }

    if ((e.code === "Enter" || e.code === "Space") && e.target.localName === "button") {
      e.preventDefault();
      this.toggleAccordion(e);
    }
  };

  handleMainClick = (e) => {
    if (e.target.localName === "button") {
      e.stopImmediatePropagation();
      this.toggleAccordion(e);
    }
  };

  closeAllAccordions() {
    const buttons = document.querySelectorAll("button");
    buttons.forEach((button) => button.setAttribute("aria-expanded", "false"));
  }

  setButtonTabIndex(nextNumber) {
    document.querySelectorAll("section button").forEach((section) => {
      section.tabIndex = -1;
      if (parseInt(section.dataset.index) === nextNumber) {
        section.tabIndex = 0;
      }
    });
  }

  toggleAccordion(event) {
    const button = event.target;

    if (button.getAttribute("aria-expanded") === "true") {
      button.setAttribute("aria-expanded", "false");
    } else {
      this.closeAllAccordions();
      button.setAttribute("aria-expanded", "true");
    }
  }


}

const accordion = new Accordion();

```
#### The Section Class:
The Section class represents a single section within the accordion. It has a createSection method that generates the HTML structure for a section, including the title and content. This method is called within the getData method of the Accordion class to populate the accordion with data from a JSON file.

#### The Accordion Class:
The Accordion class extends the Section class and adds additional functionality specific to the accordion component. Here's an overview of the important methods within this class:

- getData: This method fetches data from a JSON file (data.json) asynchronously using the Fetch API. It retrieves an array of posts and dynamically creates sections for each post using the createSection method inherited from the Section class.

- closeAllAccordions: This method closes all accordion sections by setting the aria-expanded attribute of the buttons to "false".

- toggleAccordion: This method toggles the expanded/collapsed state of an accordion section when a button is clicked. It also closes other sections by calling closeAllAccordions before expanding the clicked section.

- setButtonTabIndex: This method sets the tabIndex property of the buttons based on the next section's index. It allows keyboard navigation using the Tab key.

- handleMainPress and handleMainClick: These event handler methods listen for keyboard and mouse click events, respectively. They call the appropriate methods to handle accordion interaction based on the user's actions.


#### Handling Accessibility in the Accordion Implementation

##### `handleMainPress` Method
The `handleMainPress` method is an event handler that listens for keyboard events within the accordion's main container. Its purpose is to handle keyboard navigation and expand/collapse behavior. Let's look at the key aspects of this method:

- **Tab Navigation**: When the Tab key is pressed, the method calls the `setButtonTabIndex` method to manage the `tabIndex` attribute of the buttons within the accordion. This ensures that the focus moves in a logical order through the accordion sections.

- **Accordion Interaction**: If the Enter key or Spacebar is pressed while a button is focused, the method prevents the default action (`e.preventDefault()`) to avoid unintended browser behavior. It then calls the `toggleAccordion` method to expand or collapse the section associated with the clicked button.

##### `toggleAccordion` Method
The `toggleAccordion` method is responsible for expanding or collapsing an accordion section. Let's delve into its functionality:

- **Button State Management**: The method toggles the `aria-expanded` attribute of the clicked button to indicate the expanded or collapsed state of the section. When a button is expanded, it sets `aria-expanded="true"`, and when it's collapsed, it sets `aria-expanded="false"`.

- **Closing Other Accordions**: To ensure that only one section is open at a time, the method calls the `closeAllAccordions` method to close all other sections before expanding the clicked section. This enhances usability and prevents users from getting lost within the accordion.

### Animating accordion using css
Here i am adding transition to grid-template-rows. By default, it will be 0fr(don't set in pixels), once the user clicks on button it will be 1fr.

```css
main section .content {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.6s;
}

main section:has(button[aria-expanded="true"]) button {
  outline-color: lightcyan;
  background-color: lightskyblue;
}

main section:has(button[aria-expanded="true"]) .content {
  grid-template-rows: 1fr;
}

main section:has(button[aria-expanded="true"]) .heading::after {
  rotate: 0deg;
}
```


---------
By implementing proper accessibility features in accordions, we can make them usable for all users, regardless of their abilities or the assistive technologies they rely on. If you have any queries, let me know in the comments