DEV Community

Cover image for Creating an Animated and Accessible Accordion with JavaScript, HTML, and CSS
Sumukhakb
Sumukhakb

Posted on • Updated on

Creating an Animated and Accessible Accordion with JavaScript, HTML, and CSS

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

Prerequisites:

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

Some Important links:

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.

<!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>
Enter fullscreen mode Exit fullscreen mode

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

JavaScript Implementation:

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

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();

Enter fullscreen mode Exit fullscreen mode

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.

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;
}
Enter fullscreen mode Exit fullscreen mode

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

Top comments (3)

Collapse
 
diomed profile image
May Kittens Devour Your Soul

your demo is not working.
you put data as js file instead of json.
and you messed up link to real js file.

Collapse
 
sumukhakb210 profile image
Sumukhakb

Thanks for letting me know, while pushing the code again, i messed up. Can you check it once again?

Collapse
 
charlesbelov profile image
Charles Belov

Please update your animation code to respect prefers-reduced-motion. This is a best practice that if I've set my OS to indicate I don't want animation, you don't serve me animation. Supported by Chrome, Safari, Firefox, Edge.