Building an Accessible Accordion Menu with HTML, CSS, and JavaScript

Accordion menus are a popular design pattern for good reason. They allow you to present a large amount of content in a compact, organized way. By hiding content behind clickable headers, accordions save precious screen real estate while maintaining content discoverability.

Here are a few reasons why accordions are so useful:

  1. Conserve space – Content is compressed until needed, perfect for mobile screens.
  2. Organize information – Related content is grouped logically, aiding scannability.
  3. Improve UX – Users can quickly navigate to the content they need without being overwhelmed.
  4. Increase engagement – Interactivity encourages users to explore the content.

A study by the NN Group found that accordions can make content 50-150% more scannable, leading to improved content discoverability and engagement (source).

In this tutorial, we‘ll learn how to build a fully-accessible accordion menu from scratch using semantic HTML, CSS, and JavaScript. Let‘s dive in!

Semantic HTML Structure

The foundation of an accessible accordion is semantic HTML. This means using elements that accurately reflect the content‘s structure and meaning.

Instead of generic <div> elements, we‘ll use the <details> and <summary> elements to represent the accordion:

<details class="accordion-item">
  <summary class="accordion-header">
    <h3>Section 1</h3>
  </summary>
  <div class="accordion-content">
    <p>Section 1 content...</p>
  </div>
</details>

The <details> element represents a disclosure widget, where information is only visible when toggled into an "open" state. The <summary> element specifies the header that‘s always visible.

For the header text itself, we‘ll use a heading element (in this case, <h3>). This establishes a proper document outline and aids navigation for assistive technology users.

Inside the <details> element, we can put any type of content – paragraphs, lists, images, and so on. This content will be hidden until the user expands the accordion item.

CSS Layout and Styling

With our semantic HTML in place, let‘s use CSS to make the accordion functional and visually appealing.

Basic Layout

First, we‘ll set up the basic accordion layout using flexbox:

.accordion {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.accordion-item {
  background-color: #fff;
  border: 1px solid #ccc;
  border-radius: 4px;
}

This stacks the accordion items vertically with some space between them. We‘ve also added a border and background color to visually separate each item.

Header Styles

Next, let‘s style the accordion headers:

.accordion-header {
  display: flex;
  align-items: center;
  padding: 1rem;
  cursor: pointer;
  transition: background-color 0.3s;
}

.accordion-header:hover {
  background-color: #f4f4f4;
}

.accordion-header::before {  
  content: ‘▸‘;
  margin-right: 1rem;
  transition: transform 0.3s;
}

details[open] .accordion-header::before {
  transform: rotate(90deg);
}

Here‘s what we‘re doing:

  1. Set the headers to display: flex to allow centering of the marker icon and text
  2. Add padding, hover styles, and a pointer cursor for interactivity
  3. Use a pseudo-element for the marker icon, which rotates when the item is open
  4. Transition the marker icon and background color for a smooth animation

Content Styles

Finally, let‘s style the expandable content area:

.accordion-content {
  overflow: hidden;
  padding: 0 1rem;
  height: auto;
  max-height: 0;
  transition: max-height 0.3s, padding 0.3s;
}

details[open] .accordion-content {
  max-height: 1000px; /* Adjust as needed */
  padding: 1rem;

}

To create a smooth expand/collapse animation, we‘re combining a few techniques:

  1. overflow: hidden to cut off content when collapsed
  2. max-height: 0 initially, which transitions to a large value when open
  3. Transitioning max-height and padding for a smooth animation

You can adjust the max-height value to accommodate your longest content. Alternatively, you can use JavaScript to set it dynamically based on the content‘s actual height.

Accessibility Enhancements

Our accordion is already quite accessible thanks to the semantic HTML structure. However, we can further improve the accessibility with a few additions.

ARIA Attributes

ARIA (Accessible Rich Internet Applications) attributes help convey the accordion‘s state and behavior to assistive technologies.

First, let‘s add a role="region" to the <details> element. This identifies the expandable content as a perceivable section:

<details class="accordion-item" role="region">
  ...
</details>

Next, we‘ll link the headers and content areas with aria-controls and id attributes:

<summary class="accordion-header" aria-controls="section1">
  <h3>Section 1</h3>  
</summary>
<div class="accordion-content" id="section1">
  ...
</div>

Finally, we‘ll toggle the aria-expanded state on the headers with JavaScript:

const accordionHeaders = document.querySelectorAll(‘.accordion-header‘);

accordionHeaders.forEach(header => {
  header.addEventListener(‘click‘, () => {
    const isExpanded = header.getAttribute(‘aria-expanded‘) === ‘true‘;
    header.setAttribute(‘aria-expanded‘, !isExpanded);
  });
});

These ARIA enhancements make the accordion‘s semantics and state clearer to screen reader users.

Keyboard Navigation

For keyboard users, we should ensure the accordion is fully navigable and operable with the keyboard alone.

By default, the <summary> element is keyboard-focusable and toggles the accordion item when pressed with Enter or Space. However, we can enhance the navigation further:

const accordionItems = document.querySelectorAll(‘.accordion-item‘);

accordionItems.forEach(item => {
  const header = item.querySelector(‘.accordion-header‘);

  header.addEventListener(‘keydown‘, (event) => {
    const isOpen = item.hasAttribute(‘open‘);

    switch (event.key) {
      case ‘ArrowUp‘:
        const prevItem = item.previousElementSibling;
        if (prevItem) prevItem.querySelector(‘.accordion-header‘).focus();
        break;

      case ‘ArrowDown‘:
        const nextItem = item.nextElementSibling;
        if (nextItem) nextItem.querySelector(‘.accordion-header‘).focus();
        break;

      case ‘Home‘:
        accordionItems[0].querySelector(‘.accordion-header‘).focus();
        break;

      case ‘End‘:
        accordionItems[accordionItems.length - 1].querySelector(‘.accordion-header‘).focus();
        break;
    }
  });
});

This script allows users to navigate between accordion headers with the arrow keys, and jump to the first or last item with the Home and End keys.

When combined with the default Enter/Space behavior, this provides a complete and intuitive keyboard experience.

Performance Optimization

Accordions are generally lightweight components, but there are a few ways to optimize their performance.

Lazy Loading

If your accordion contains many high-resolution images or complex content, it may be worth lazy loading the content for each item. This means only loading the content when the item is expanded.

Here‘s a basic example using JavaScript:

<details class="accordion-item">
  <summary class="accordion-header">Section 1</summary>
  <div class="accordion-content" data-src="content1.html"></div>
</details>
const accordionItems = document.querySelectorAll(‘.accordion-item‘);

accordionItems.forEach(item => {
  item.addEventListener(‘toggle‘, () => {
    const content = item.querySelector(‘.accordion-content‘);
    const src = content.getAttribute(‘data-src‘);

    if (item.hasAttribute(‘open‘) && src) {
      fetch(src)
        .then(response => response.text())
        .then(html => {
          content.innerHTML = html;
          content.removeAttribute(‘data-src‘);
        });
    }
  });
});

This script fetches the content from an external HTML file only when the item is first opened, reducing the initial page load.

Measuring Performance

To get a quantitative measure of your accordion‘s performance, you can use tools like Lighthouse or WebPageTest.

These tools will analyze your page‘s load time, resource usage, and other metrics. They‘ll also provide suggestions for improving performance, such as:

  • Minifying CSS and JavaScript files
  • Compressing images
  • Leveraging browser caching
  • Eliminating render-blocking resources

By implementing these optimizations, you can ensure your accordion loads quickly and efficiently.

Real-World Usage

Accordions are incredibly versatile components that can be used in a wide variety of real-world applications. Some common use cases include:

  • FAQs – Organize frequently asked questions into expandable categories.
  • Product Specifications – Break down complex product info into digestible sections.
  • User Profiles – Hide less critical user details behind expandable headers.
  • Course Curriculums – Structure course content into collapsible modules or lessons.
  • Checkout Processes – Guide users through a multi-step checkout flow.
  • Mobile Navigation – Conserve screen space with expandable menu items.

No matter the use case, accordions provide a clean, user-friendly way to present complex information.

Conclusion

In this tutorial, we‘ve learned how to build a fully-accessible accordion component from the ground up.

We started with a semantic HTML foundation, layered on functional and appealing CSS styles, and finished with accessibility and performance enhancements via JavaScript.

The end result is a reusable, flexible component that can be easily integrated into any web project.

To recap, here are the key takeaways:

  1. Use <details> and <summary> elements for the accordion structure.
  2. Style with CSS to create an appealing, functional design.
  3. Enhance with JavaScript for optimal accessibility and performance.
  4. Ensure the accordion is keyboard-navigable and screen reader friendly.
  5. Optimize performance through lazy loading and other techniques.

With these principles in mind, you‘ll be well-equipped to create accordions that are both user-friendly and developer-friendly.

As next steps, I encourage you to:

  1. Experiment with different designs and animations.
  2. Test the accordion with various assistive technologies.
  3. Integrate the accordion into your own web projects.
  4. Share your accordion implementations with the dev community!

If you have any questions or suggestions, feel free to reach out. Happy coding!

Similar Posts