// pattern

Accessible Disclosure Pattern

A single show/hide widget built on the native <details> and <summary> elements. Zero JavaScript, full keyboard support, and screen reader state announcements for free.

native-html zero-js wcag-2.2-aa

// 01 · live demo

Live Demo

Click the trigger or press Enter/Space when focused to reveal the hidden content.

Show shipping details

Orders ship within 2 business days via standard mail. Tracking information is emailed as soon as the label is printed. International shipping is available to most countries — see the full list at checkout.

Inline "read more" variant:

A disclosure widget reveals supplemental content in place. It is the native HTML show/hide pattern and the simplest accessible expand/collapse you can build.

Read more Unlike a modal, it doesn't trap focus. Unlike a tooltip, the content is persistent once opened. Unlike an accordion, it is one section, not a group. Unlike a dropdown menu, the content inside is static, not a list of commands.

Inaccessible version (span-based):

Show shipping details

This trigger can't be reached with the Tab key, doesn't announce an expanded/collapsed state to screen readers, and doesn't respond to Enter or Space.

// 02 · the code

The Code

HTML
<!-- Block disclosure (closed by default) -->
<details class="disclosure">
  <summary>
    Show shipping details
    <svg class="disclosure-icon" aria-hidden="true">
      <!-- Chevron icon that rotates when open -->
    </svg>
  </summary>
  <div class="disclosure-content">
    <p>Hidden content revealed on expand.</p>
  </div>
</details>

<!-- Open by default: add the open attribute -->
<details class="disclosure" open>
  <summary>Open by default</summary>
  <div class="disclosure-content">
    <p>Visible when the page loads.</p>
  </div>
</details>

<!-- Inline "read more" inside a paragraph -->
<p>
  A short summary sentence.
  <details class="inline-disclosure">
    <summary>Read more</summary>
    <span> The rest of the text continues here
      with more detail.</span>
  </details>
</p>
CSS
.disclosure {
  border: 1px solid #d1d5db;
  border-radius: 0.75rem;
  overflow: hidden;
}

/* Remove the default disclosure triangle */
.disclosure summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 1.5rem;
  cursor: pointer;
  font-weight: 600;
  list-style: none;
  /* SC 2.5.8: minimum 24x24 target */
  min-height: 2.75rem;
}

.disclosure summary::-webkit-details-marker {
  display: none;
}

.disclosure summary::marker {
  content: "";
}

.disclosure summary:hover {
  background: #f8f9fa;
}

/* Focus style: SC 2.4.13 compliant */
.disclosure summary:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}

/* Chevron rotates when section is open */
.disclosure-icon {
  width: 1.25rem;
  height: 1.25rem;
  flex-shrink: 0;
  transition: transform 200ms ease;
}

.disclosure details[open] .disclosure-icon,
details.disclosure[open] .disclosure-icon {
  transform: rotate(180deg);
}

.disclosure-content {
  padding: 0 1.5rem 1.5rem;
}

/* Inline "read more" variant */
.inline-disclosure {
  display: inline;
}

.inline-disclosure summary {
  display: inline;
  cursor: pointer;
  color: var(--color-primary);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.inline-disclosure summary::-webkit-details-marker {
  display: none;
}

.inline-disclosure[open] summary {
  display: none;
}

/* Respect reduced motion */
@media (prefers-reduced-motion: reduce) {
  .disclosure-icon {
    transition: none;
  }
}
No JavaScript file needed The entire disclosure works with HTML and CSS only. The <details> element handles toggling, keyboard activation, and state announcements natively.

// 03 · disclosure vs. accordion

Disclosure vs. Accordion

A disclosure is a single show/hide widget. An accordion is a group of disclosures that share visual treatment and often a single coordinated behavior.

Use case Pattern
One standalone "read more" or "show details" section Disclosure
Multiple related sections under the same heading Accordion
FAQ with 6+ questions under a single heading Accordion
Form that reveals an optional field group when checked Disclosure
Revealing extra text inside a paragraph Disclosure (inline)
Showing a hidden menu of actions Dropdown Menu (not disclosure)
Revealing a short helper string on focus/hover Tooltip (not disclosure)
Both use the same primitive Accordions are just disclosures stacked. If you're building a single expand/collapse, use this pattern. If you're building a group of them, jump to the accordion pattern.

// 04 · why these decisions

Why These Decisions

Why <details>/<summary> instead of a button and a div?

A <button aria-expanded> plus a hidden <div> is the manual equivalent — and it requires JavaScript to toggle, ARIA to communicate state, and CSS to hide content. The native <details> does all of this automatically: the <summary> is focusable, Enter/Space toggles it, and the expanded/collapsed state is exposed to assistive tech via the open attribute.

Why a descriptive summary label?

The summary text is the only preview a user has of what's hidden. "Read more" forces users to click to find out — and that's a problem for screen reader users scanning by links. "Show shipping details," "Read the full bio," or "See refund policy" are all better because they describe the revealed content.

Why replace the default triangle?

The browser's default marker is tiny, varies across browsers, and is easy to miss. A larger chevron icon that rotates when open is a widely understood convention that satisfies target size requirements (SC 2.5.8) and provides a clearer visual affordance.

Why not put interactive elements inside <summary>?

A <summary> is itself a focusable button. Nesting another button, link, or form control inside it produces focus order bugs and ambiguous click targets. Keep the summary plain text (optionally with non-interactive icons marked aria-hidden).

// 05 · keyboard interaction

Keyboard Interaction

Key Action
Tab Moves focus to the <summary> element.
Shift + Tab Moves focus to the previous focusable element.
Enter Toggles the <details> open or closed.
Space Toggles the <details> open or closed.
Focus stays on the summary after toggle Activating the <summary> does not move focus. This is the correct behavior: the user is still interacting with the trigger and may want to collapse it again. If you need focus to move into the revealed content (for example, a newly visible form field), use JavaScript to do that on the toggle event.

// 06 · wcag 2.2 success criteria

WCAG 2.2 Success Criteria

This pattern satisfies the following WCAG 2.2 success criteria:

  • 1.3.1 Info and Relationships Level A — The <summary> element is programmatically associated with the revealed content inside its parent <details>.
  • 2.1.1 Keyboard Level AEnter and Space toggle the disclosure. Tab moves focus to and away from it.
  • 2.4.3 Focus Order Level A — The summary appears in the natural document tab order. Revealed content follows the summary, so Tab continues naturally into any focusable children.
  • 2.4.4 Link Purpose (In Context) Level A — Descriptive summary text (rather than generic "Read more") tells the user what will be revealed.
  • 2.4.13 Focus Appearance Level AAA — The summary has a 2px outline with 3:1 contrast against adjacent colors.
  • 2.5.8 Target Size (Minimum) Level AA — The <summary> meets the minimum 24×24px target size via padding and min-height.
  • 4.1.2 Name, Role, Value Level A — The browser exposes the expanded/collapsed state to assistive technology via the open attribute. No ARIA is needed.

// 07 · screen reader behavior

Screen Reader Behavior

When focused on a closed disclosure

  • NVDA: "Show shipping details, collapsed, summary" — announces the label, the state, and the role.
  • JAWS: "Show shipping details, collapsed" — announces the label and state.
  • VoiceOver: "Show shipping details, summary, collapsed, group" — announces label, role, state, and containing group.

When the user activates it

  • NVDA / JAWS / VoiceOver: "expanded" — the state change is announced immediately after Enter or Space.

After expansion the revealed content is part of the reading order. Tab moves to any focusable element inside, and screen reader browse modes can read through it line by line.

// 08 · common mistakes

Common Mistakes

Using a <span> or <div> with a click handler A non-interactive element with a click listener is invisible to keyboard users and screen readers. You would then have to add role="button", tabindex="0", aria-expanded, aria-controls, plus keyboard handlers for Enter and Space. Native <details>/<summary> gives you all of this automatically.
Using disclosure for a menu, tooltip, or modal Each of those has its own expected behaviors — a menu has arrow-key navigation and focus movement, a tooltip closes on blur and never contains interactive content, a modal traps focus and dims the page. Use the dropdown menu, tooltip, or modal patterns instead.
Generic "Read more" or "Click here" label Screen reader users scanning by link or focus list will hear "Read more, read more, read more" — with no context about what they're revealing. Write summary text that describes the specific content, such as "Read more about refund policy."
Nesting interactive elements inside <summary> Putting a <button>, <a>, or form control inside a summary creates a nested focusable element. Click targets become ambiguous and focus order becomes confusing. Keep the summary to plain text and decorative icons.
Hiding required content from search Content inside a closed <details> is in the DOM and indexable by search engines. That's generally a feature, but don't use disclosure to hide something the user needs to complete a task. If the content is essential, show it by default.

// 09 · native html vs. aria

Native HTML vs. ARIA

What <details>/<summary> gives you for free versus building the same pattern manually.

Feature <details>/<summary> Button + hidden div + ARIA
Expand/collapse Automatic Manual JavaScript
Keyboard activation Automatic — Enter/Space <button> handles this if used
Focusable trigger Automatic Requires <button> or tabindex
Expanded/collapsed state Automatic via open attribute Manual aria-expanded
Content association Automatic (nested inside <details>) Manual aria-controls + matching id
Default-open state <details open> Set initial aria-expanded="true" and show content
JavaScript required None Yes — toggle logic and state sync
The verdict Unless you have a specific reason to use ARIA (for example, you're retrofitting a codebase where <details> isn't an option), the native element wins on every dimension.