// 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.
// 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):
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
<!-- 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>
.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;
}
}
<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) |
// 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. |
<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 A — Enter 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
openattribute. 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
<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.
<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.
<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 |
<details> isn't an option), the native element wins on every dimension.