Accordion (Collapsible Sections)

Create accessible accordion components with proper ARIA attributes, keyboard navigation, and screen reader support.

intermediate
4.1.2 Name, Role, Value (Level A)3.2.1 On Focus (Level A)2.4.3 Focus Order (Level A)

The Bad (Inaccessible)

<div class="accordion-item" onclick="toggle(this)">
  <div class="accordion-header">Section 1</div>
  <div class="accordion-content" style="display:none">
    Content
  </div>
</div>

Accessibility-Ready Code

<!-- Gold Standard: Proper button with ARIA states -->
<div class="accordion">
  <!-- Item 1 -->
  <div class="accordion-item">
    <h3>
      <button
        type="button"
        aria-expanded="false"
        aria-controls="panel-1"
        class="accordion-trigger"
      >
        <span class="accordion-title">What is an accordion?</span>
        <span class="accordion-icon" aria-hidden="true">▼</span>
      </button>
    </h3>
    <div
      id="panel-1"
      role="region"
      aria-labelledby="panel-1-heading"
      class="accordion-panel"
      hidden
    >
      <p id="panel-1-heading" class="sr-only">
        What is an accordion?
      </p>
      <p>
        An accordion is a vertically stacked list of headers that reveal
        or hide content associated with them.
      </p>
    </div>
  </div>

  <!-- Item 2 -->
  <div class="accordion-item">
    <h3>
      <button
        type="button"
        aria-expanded="false"
        aria-controls="panel-2"
        class="accordion-trigger"
      >
        <span class="accordion-title">When to use accordions?</span>
        <span class="accordion-icon" aria-hidden="true">▼</span>
      </button>
    </h3>
    <div
      id="panel-2"
      role="region"
      aria-labelledby="panel-2-heading"
      class="accordion-panel"
      hidden
    >
      <p id="panel-2-heading" class="sr-only">
        When to use accordions?
      </p>
      <p>
        Use accordions to reduce scrolling when you have related content
        that users don't need to see simultaneously.
      </p>
    </div>
  </div>
</div>

<script>
  document.querySelectorAll('.accordion-trigger').forEach(button => {
    button.addEventListener('click', () => {
      const isExpanded = button.getAttribute('aria-expanded') === 'true';
      const panel = document.getElementById(
        button.getAttribute('aria-controls')
      );

      button.setAttribute('aria-expanded', !isExpanded);
      panel.hidden = isExpanded;
    });
  });
</script>

The Standard

Accordions are collapsible sections that show/hide content. They must use proper button elements, ARIA states, and support keyboard navigation.

WCAG Criteria

  • 4.1.2 Name, Role, Value: Proper ARIA states.
  • 3.2.1 On Focus: No focus changes without user interaction.
  • 2.4.3 Focus Order: Logical tab order.

❌ The Bad (Inaccessible)

What’s Wrong?

  1. Using onclick on div: Not keyboard accessible.
  2. No aria-expanded: Screen readers can’t determine state.
  3. Missing panel role: Screen reader loses context.
  4. No keyboard navigation: Can’t navigate between items.

✅ The Good (Accessibility-Ready Code)

Why This Works

  1. Proper Button Element: Inherently keyboard accessible.
  2. Dynamic aria-expanded: Updates to show panel state.
  3. Region Role: Provides context for screen readers.
  4. aria-labelledby: Associates panel with its header.

Accessibility Checklist

  • Use <button> instead of <div> for headers.
  • Add aria-expanded that updates (true/false).
  • Include aria-controls pointing to panel ID.
  • Add role="region" to content panels.
  • Use aria-labelledby to link panel to header.
  • Support arrow keys for navigation.
  • Add visual indicator (▼/▲) with aria-hidden="true".
📍 Perfect Implementation Reference
<div class="accordion-item">
  <h3>
    <button
      type="button"
      aria-expanded="false"
      aria-controls="panel-1"
    >
      Section Title
      <span aria-hidden="true">▼</span>
    </button>
  </h3>
  <div
    id="panel-1"
    role="region"
    aria-labelledby="panel-1-heading"
    hidden
  >
    <p id="panel-1-heading" class="sr-only">
      Section Title
    </p>
    <p>Content goes here...</p>
  </div>
</div>

<script>
  button.addEventListener('click', () => {
    const isExpanded = button.getAttribute('aria-expanded') === 'true';
    button.setAttribute('aria-expanded', !isExpanded);
    panel.hidden = isExpanded;
  });
</script>

Technical Deep Dive

Screen Reader Announcements

  • NVDA: “What is an accordion? button, collapsed”
  • VoiceOver: “What is an accordion?, button, collapsed”
  • JAWS: “What is an accordion? button, collapsed”

Best Practice: When to Use Accordions

Accordions are best for reducing scrolling when you have related content that users don’t need to see simultaneously. They’re not ideal for content that all users need to read, as the hidden content may be overlooked. If in doubt, show all content by default.

🧪

Interactive Behavioral Lab

💻

Interactive Sandbox

LIVE PREVIEW

🔬 Technical Internals

Understand how this component is processed by the browser and Assistive Technology (AT). This section bridges the gap between visual code and the hidden logic that powers accessibility.

🌲 Accessibility Tree

The data structure used by screen readers to "see" your page. It translates HTML roles and attributes into standardized objects.

⚙️ Event Logic

Expected behavioral standards for keyboard navigation and state transitions. Crucial for users who don't use a mouse.

  • Focus: Highlights via :focus-visible
  • Activation: Responds to Enter and Space
  • Role: Identified as Accordion

🌐 Browser & Screen Reader Compatibility

Browser
Screen Reader
Status
🍎 Safari
VoiceOver
✓ Safe
🪟 Chrome
NVDA
✓ Safe
🪟 Edge
JAWS
✓ Safe
🦊 Firefox
NVDA
✓ Safe
📱 iOS Safari
VoiceOver
✓ Safe
🤖 Chrome Android
TalkBack
✓ Safe