Skip to Content
Component ExamplesDisclosure Widgets

Overview

Disclosure widgets reveal and hide supplementary content. The simplest and most robust approach is the native <details>/<summary> element, which provides keyboard support and screen reader announcements with zero JavaScript. When a custom design requires more control, a <button> with aria-expanded and a toggled panel is the correct fallback — never a link or a plain <div>.

WCAG Criteria:

Key requirements:

  • <details>/<summary> is the preferred approach — built-in keyboard and screen reader support with no JavaScript
  • Always use a <button> for custom disclosure triggers, never a link or div
  • Use aria-expanded to communicate the open/closed state
  • Use aria-controls to associate the trigger with the panel (aria-controls has limited screen reader support but is recommended for code clarity)
  • Toggle the hidden attribute on the panel so it is fully removed from the accessibility tree when closed

Native Details/Summary

Native Details/Summary

Inaccessible
<!-- Link toggle — wrong element, no state, no keyboard semantics --> <h3>More Information</h3> <div id="content" style="display:none"> Our product is made from 100% recycled materials... </div> <a href="#" onclick="toggle(); return false">Show more</a>
Live Preview

More Information

Show more
Accessible
<!-- Native details/summary — zero JS, full keyboard + SR support --> <details> <summary>More Information</summary> <div> Our product is made from 100% recycled materials... </div> </details>
Live Preview
More Information
Our product is made from 100% recycled materials. It is designed to last for years and comes with a lifetime warranty. Contact support for more details.

What’s wrong with the link toggle?

  • A link (<a href="#">) is semantically a navigation element, not a toggle — screen readers announce it as “link” rather than a button or disclosure
  • No aria-expanded state — screen readers cannot tell whether the content is currently visible or hidden
  • The link text changes between “Show more” and “Show less,” which can confuse speech recognition users who need a stable name
  • Using onclick with return false on a link is a hack — keyboard users who press Enter may trigger unexpected scroll-to-top behavior
  • The content is hidden with display:none via inline style rather than the hidden attribute, which works but bypasses the native disclosure pattern entirely

What the screen reader announces:

VersionAnnouncement
Inaccessible (<a> toggle)“Show more, link” (no expanded/collapsed state)
Accessible (<details>)“More Information, collapsed, summary” / “More Information, expanded, summary”

Custom Disclosure

Custom Disclosure

Inaccessible
<!-- Div trigger — no role, no state, no keyboard access --> <div class="trigger" onclick="togglePanel()"> FAQ Answer </div> <div class="panel" style="display:none"> You can cancel your subscription at any time... </div>
Live Preview
FAQ Answer
Accessible
<!-- Button with aria-expanded + hidden panel --> <button aria-expanded="false" aria-controls="disclosure-good-panel" onclick=" var expanded = this.getAttribute('aria-expanded') === 'true'; this.setAttribute('aria-expanded', String(!expanded)); var panel = document.getElementById('disclosure-good-panel'); if (!expanded) { panel.removeAttribute('hidden'); } else { panel.setAttribute('hidden', ''); } " > FAQ Answer </button> <div id="disclosure-good-panel" role="region" hidden> You can cancel your subscription at any time... </div>
Live Preview

What’s wrong with the div trigger?

  • A <div> has no button role — screen readers do not identify it as interactive
  • It cannot receive keyboard focus with Tab
  • It cannot be activated with Enter or Space
  • No aria-expanded means the open/closed state is invisible to assistive technology
  • onclick on a div only works with a mouse

What the screen reader announces:

VersionAnnouncement
Inaccessible (<div>)“FAQ Answer” (just text, no role, no state)
Accessible (<button>)“FAQ Answer, collapsed, button”
When expanded”FAQ Answer, expanded, button”

When to Use Which Pattern

PatternElementJS requiredUse when
Native disclosure<details> + <summary>NoSimple show/hide content — FAQ answers, supplementary info, expandable sections
Custom disclosure<button> + aria-expandedYesYou need custom animation, styling, or behavior that <details> cannot support

Key differences:

  • <details>/<summary> provides keyboard support, focus management, and expanded/collapsed state announcements automatically with zero JavaScript
  • Custom disclosure requires manually managing aria-expanded, the hidden attribute, and keyboard interaction — use only when you need control that native elements cannot provide
  • aria-controls associates the button with its panel; while screen reader support for aria-controls is limited, it is recommended for developer clarity and future compatibility
  • Never use a link (<a>) or plain <div> as a disclosure trigger — links are for navigation, divs are not interactive

Resources