What Is an Accessible Accordion?

Accordions are a common user interface pattern that allow content to be shown and hidden by clicking a header. When built without consideration for assistive technologies, these components become unusable for people who rely on screen readers or keyboard navigation. An accessible accordion uses semantic HTML and ARIA (Accessible Rich Internet Applications) attributes to communicate state and purpose to all users.

This guide walks through creating a fully accessible accordion from scratch using JavaScript and ARIA roles. We will cover the HTML structure, ARIA attributes, JavaScript behavior, keyboard interaction, and styling practices that meet the Web Content Accessibility Guidelines (WCAG).

Understanding ARIA Roles and States in an Accordion

ARIA provides a set of attributes that bridge the gap between HTML semantics and dynamic behavior. For an accordion, the most critical attributes are:

  • role="region" – Marks the content panel as a standalone region. This helps screen readers identify the panel as a distinct part of the page.
  • aria-expanded – Placed on the button element. A value of true indicates the panel is open; false means collapsed. This attribute is updated by JavaScript on each toggle.
  • aria-controls – On the button, references the id of the panel it controls. This associate the button with the panel.
  • aria-labelledby – On the panel, references the id of the button. This gives the panel an accessible name derived from the button text.

The WAI-ARIA Authoring Practices provide a recommended pattern for accordions. According to that pattern, each accordion header should be a button, and the panel should be hidden using the hidden attribute (or CSS display: none). Using hidden ensures the content is removed from the accessibility tree when collapsed.

HTML Structure: Building a Semantic Foundation

A well‑structured accordion starts with semantic HTML elements. Use <button> for the toggle triggers, <div> for panels, and wrap each item in a container. Below is an example that follows the ARIA recommendations:

<div class="accordion">
  <div class="accordion-item">
    <h3>
      <button class="accordion-header"
              id="accordion-label-1"
              aria-expanded="false"
              aria-controls="accordion-panel-1">
        Section One
      </button>
    </h3>
    <div id="accordion-panel-1"
         role="region"
         aria-labelledby="accordion-label-1"
         hidden>
      <p>Content for the first section.</p>
    </div>
  </div>
  <div class="accordion-item">
    <h3>
      <button class="accordion-header"
              id="accordion-label-2"
              aria-expanded="false"
              aria-controls="accordion-panel-2">
        Section Two
      </button>
    </h3>
    <div id="accordion-panel-2"
         role="region"
         aria-labelledby="accordion-label-2"
         hidden>
      <p>Content for the second section.</p>
    </div>
  </div>
</div>

Notice that we wrap each button in a heading element (<h3>). This preserves a logical heading hierarchy and helps screen reader users navigate the page structure. The heading level should match the document outline (e.g., <h2> if the accordion falls under an <h2>).

An alternative structure uses <dl> (description list) with <dt> for headers and <dd> for panels, but the button‑based approach is more common and easier to style. Whichever method you choose, ensure the ARIA attributes are correctly applied.

Styling with Accessibility in Mind

CSS plays a role in making the accordion usable for people who rely on visual cues. Key styling considerations include:

  • Focus indicators – Remove the default outline only if you provide a more visible custom focus style. Use :focus-visible to show a focus ring only when the user navigates via keyboard.
  • Hover and active states – Provide visual feedback on hover and active (mousedown) to help all users understand the element is interactive.
  • Transitions for smooth open/close – Use max-height or transform animations to avoid jarring layout shifts. Animate the hidden property by using CSS classes instead of the hidden attribute if you want transitions.

Example CSS snippet for a basic accessible design:

.accordion-header {
  display: block;
  width: 100%;
  padding: 0.75rem 1rem;
  font-size: 1rem;
  font-weight: bold;
  background: #f5f5f5;
  border: 1px solid #ccc;
  cursor: pointer;
  text-align: left;
}

.accordion-header:focus-visible {
  outline: 2px solid #005fcc;
  outline-offset: 2px;
}

.accordion-panel {
  border: 1px solid #ccc;
  border-top: none;
  padding: 0.75rem 1rem;
}

.accordion-panel[hidden] {
  display: none; /* or visibility: hidden; but display: none works with screen readers */
}

JavaScript: Managing State and Interactions

The core JavaScript logic must update ARIA attributes and toggle panel visibility. A clean approach is to create a reusable function that handles the toggle for a single item, and optionally supports "only one open" behavior.

function toggleAccordion(button) {
  const expanded = button.getAttribute('aria-expanded') === 'true';
  const panelId = button.getAttribute('aria-controls');
  const panel = document.getElementById(panelId);

  // Toggle the button state
  button.setAttribute('aria-expanded', !expanded);

  // Toggle panel visibility
  if (expanded) {
    panel.setAttribute('hidden', '');
    panel.classList.remove('accordion-panel--open');
  } else {
    panel.removeAttribute('hidden');
    panel.classList.add('accordion-panel--open');
  }
}

// Attach event listeners
document.addEventListener('click', function(event) {
  const button = event.target.closest('.accordion-header');
  if (button) {
    // Optional: close other panels (single open mode)
    // document.querySelectorAll('.accordion-header').forEach(b => {
    //   if (b !== button && b.getAttribute('aria-expanded') === 'true') {
    //     toggleAccordion(b);
    //   }
    // });
    toggleAccordion(button);
  }
});

Keyboard support – By using <button> elements, you get native keyboard interaction: users can tab to the button and press Enter or Space to activate it. However, the ARIA accordion pattern also recommends adding arrow key navigation between multiple accordion headers when focus is inside the accordion set. This helps screen reader users move quickly between items. Here is an extension that adds arrow key support:

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

accordionItems.forEach((item, index) => {
  const button = item.querySelector('.accordion-header');
  button.addEventListener('keydown', function(event) {
    let targetButton = null;
    switch (event.key) {
      case 'ArrowDown':
        event.preventDefault();
        if (index < accordionItems.length - 1) {
          targetButton = accordionItems[index + 1].querySelector('.accordion-header');
        } else {
          targetButton = accordionItems[0].querySelector('.accordion-header');
        }
        break;
      case 'ArrowUp':
        event.preventDefault();
        if (index > 0) {
          targetButton = accordionItems[index - 1].querySelector('.accordion-header');
        } else {
          targetButton = accordionItems[accordionItems.length - 1].querySelector('.accordion-header');
        }
        break;
      case 'Home':
        event.preventDefault();
        targetButton = accordionItems[0].querySelector('.accordion-header');
        break;
      case 'End':
        event.preventDefault();
        targetButton = accordionItems[accordionItems.length - 1].querySelector('.accordion-header');
        break;
    }
    if (targetButton) {
      targetButton.focus();
    }
  });
});

These keyboard shortcuts bring the experience in line with native application patterns. Always test with a screen reader to confirm that announcements are clear.

Testing for Accessibility

After building your accordion, verify its accessibility using both automated tools and manual testing.

  • Automated testing – Run axe DevTools, WAVE, or Lighthouse to catch missing ARIA attributes, color contrast issues, and focus problems.
  • Screen reader testing – Use NVDA (Windows), VoiceOver (macOS/iOS), or JAWS to listen to the accordion. Ensure that when a panel opens, the screen reader announces the heading and the expanded state.
  • Keyboard only – Navigate through the accordion using Tab and Shift+Tab. Verify that every button is reachable and that Space/Enter toggles the panel. Test arrow key navigation if implemented.

Common pitfalls include forgetting to set aria-labelledby on panels, using display: none without the hidden attribute (which can confuse some older screen readers), and not managing focus when a panel closes (focus should remain on the button, not jump to the body).

Advanced Considerations: Single vs. Multiple Open

The WAI-ARIA pattern allows both “single open” and “multiple open” accordions. In single‑open mode, opening one panel automatically closes any other open panel. This is common for FAQs or navigation menus. In the JavaScript example above, the commented code inside the event listener shows how to close all other panels before toggling the current one.

For multiple‑open mode, simply remove that logic. Both patterns are accessible as long as the ARIA state is updated correctly. Some accordions also allow programmatic opening (e.g., via URL hash). If you support that, ensure the aria-expanded and hidden attribute are synced on page load.

Progressive Enhancement and Graceful Degradation

Your accordion should work even if JavaScript fails (e.g., due to a network error or script error). A robust approach is to start with all panels visible by default, then use JavaScript to collapse them if the browser supports modern JavaScript. You can add a no-js class to the <html> and remove it via JS to show the collapsed version. Alternatively, keep the initial HTML with hidden attributes, but that means content is hidden before JavaScript runs – acceptable if you have a fallback.

Better yet, use a technique where the accordion is initially rendered as all open (no hidden), then JavaScript runs to close them. This ensures screen reader users who do not execute scripts can still read all content.

Real‑World Resources and Examples

For further reading, refer to these authoritative sources:

These resources provide complete examples, test files, and additional guidance for complex requirements like animations and dynamic content loading.

Conclusion

Building an accessible accordion component requires careful attention to HTML semantics, ARIA roles and attributes, JavaScript behavior, and keyboard interaction. By following the patterns and best practices outlined above, you can create a component that works for everyone, regardless of how they navigate the web. The effort invested in accessibility pays off in a more inclusive user experience and often leads to cleaner, more maintainable code.

Start with the semantic HTML structure, add ARIA attributes to bridge the gap between static and dynamic content, and enhance with JavaScript that respects both mouse and keyboard users. Test thoroughly with real assistive technologies, and iterate based on feedback. The result will be an accordion that not only functions well visually but also provides equal access to all users.