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:
- Repo Link:- https://github.com/Sumukha210/accordion
- Check out Live Demo
- 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
- 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.
<!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
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();
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 thetabIndex
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 thetoggleAccordion
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 setsaria-expanded="true"
, and when it's collapsed, it setsaria-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;
}
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)
your demo is not working.
you put data as js file instead of json.
and you messed up link to real js file.
Thanks for letting me know, while pushing the code again, i messed up. Can you check it once again?
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.