HTML Details & Summary Reference
Complete reference for the HTML details and summary disclosure elements — behavior, events, accordion, styling, and accessibility
You’re building an FAQ section and reach for a JavaScript accordion library — 15 KB minified, custom event handlers, ARIA attributes you have to manage manually. Then you realize there’s a native HTML element that does the same thing: <details> and <summary>. Zero JavaScript, built-in keyboard navigation, screen reader support out of the box. The catch: the exclusive accordion (name attribute), ::marker styling, CSS animations for open/close, and the toggle/beforetoggle events aren’t well documented in one place.
Why This Reference (Not the HTML Dialog Element Guide)
PureDevTools has an HTML Dialog Element Guide for modal and non-modal dialogs (<dialog>). This reference covers <details> and <summary> — disclosure widgets (collapsible sections, FAQs, accordions). Both are native HTML elements that reduce JavaScript dependency, but they solve different problems: dialogs overlay the page and trap focus; details/summary toggle inline content visibility.
HTML Details & Summary Reference
The <details> and <summary> elements are the browser-native way to build disclosure widgets — collapsible content sections that require zero JavaScript for basic toggle behavior. They power FAQs, accordions, spoilers, and optional content panels with built-in accessibility.
<!-- Minimal disclosure widget -->
<details>
<summary>What is the details element?</summary>
<p>A native HTML disclosure widget — collapsible content with no JavaScript needed.</p>
</details>
Open and Close Behavior
The <details> element is a disclosure widget. Clicking the <summary> toggles it between open and closed:
- Closed — only the
<summary>is visible - Open — all content inside
<details>is rendered
<!-- Closed by default -->
<details>
<summary>Read more</summary>
<p>This is shown when opened.</p>
</details>
<!-- Open by default (use the open attribute) -->
<details open>
<summary>Already expanded</summary>
<p>Visible immediately on page load.</p>
</details>
Programmatic Control
const details = document.querySelector('details');
// Open
details.open = true;
// Close
details.open = false;
// Check state
console.log(details.open); // true or false
The toggle Event
The toggle event fires after the state changes. Use it to react to open/close without preventing it:
details.addEventListener('toggle', (event) => {
if (event.target.open) {
console.log('Opened');
} else {
console.log('Closed');
}
});
// With ToggleEvent (Chrome 114+, Firefox 108+, Safari 17.2+)
details.addEventListener('toggle', (event) => {
console.log(event.newState); // 'open' or 'closed'
console.log(event.oldState); // 'open' or 'closed'
});
The beforetoggle Event
The beforetoggle event fires before the state changes and is cancelable:
details.addEventListener('beforetoggle', (event) => {
// Prevent closing if there are unsaved changes
if (event.newState === 'closed' && hasUnsavedChanges()) {
event.preventDefault();
}
});
beforetoggle requires Chrome 114+, Firefox 130+, Safari 17.2+.
Multiple Details Elements
Without the name attribute, multiple details elements are fully independent — any number can be open simultaneously:
<!-- All independent — multiple can be open at once -->
<details>
<summary>Section A</summary>
<p>Content A</p>
</details>
<details>
<summary>Section B</summary>
<p>Content B</p>
</details>
Use this pattern when users may want to compare content across multiple sections.
Exclusive Accordion (name Attribute)
The name attribute groups details elements into an exclusive accordion — only one can be open at a time:
<details name="faq">
<summary>Question 1</summary>
<p>Answer 1</p>
</details>
<details name="faq">
<summary>Question 2</summary>
<p>Answer 2</p>
</details>
<details name="faq">
<summary>Question 3</summary>
<p>Answer 3</p>
</details>
When one panel opens, the browser automatically closes the other panels in the group. No JavaScript needed. Supported in Chrome 120+, Firefox 130+, Safari 17.6+.
JavaScript Fallback for Older Browsers
const panels = document.querySelectorAll('details[name="faq"]');
panels.forEach(panel => {
panel.addEventListener('toggle', () => {
if (panel.open) {
panels.forEach(other => {
if (other !== panel) other.open = false;
});
}
});
});
Styling the Marker (::marker)
The disclosure triangle is a CSS list-item marker. Style it with ::marker:
/* Change marker color */
summary::marker {
color: #3b82f6;
}
/* Replace marker content */
summary::marker {
content: '▶ ';
}
details[open] summary::marker {
content: '▼ ';
}
Custom Indicator with ::before
For full CSS control (transitions, transforms), remove the default marker and use ::before:
/* Remove default triangle */
summary {
list-style: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: space-between;
}
summary::-webkit-details-marker { display: none; } /* Safari */
/* Custom animated arrow */
summary::after {
content: '▶';
transition: transform 0.2s ease;
display: inline-block;
}
details[open] summary::after {
transform: rotate(90deg);
}
Accessibility
The <summary> element has an implicit ARIA role of button. The browser automatically exposes aria-expanded based on the open state — no manual ARIA needed.
Key Accessibility Requirements
- Descriptive summary text: Screen readers announce the summary text as the button label. Use clear, descriptive text.
- Keyboard navigation: Enter and Space toggle the details when summary is focused. Tab navigates in/out.
- Focus indicators: Never remove
outlinewithout a customfocus-visiblestyle.
<!-- Good: descriptive labels -->
<details>
<summary>Return and refund policy</summary>
<p>Returns accepted within 30 days of purchase.</p>
</details>
<!-- Add custom focus indicator -->
<style>
summary:focus-visible {
outline: 2px solid #3b82f6;
outline-offset: 2px;
border-radius: 2px;
}
</style>
Animating details Open/Close
Modern CSS (Chrome 117+, Firefox 129+, Safari 17.5+)
details > *:not(summary) {
opacity: 0;
transform: translateY(-6px);
transition:
opacity 0.2s ease,
transform 0.2s ease,
display 0.2s allow-discrete;
}
details[open] > *:not(summary) {
opacity: 1;
transform: translateY(0);
}
@starting-style {
details[open] > *:not(summary) {
opacity: 0;
transform: translateY(-6px);
}
}
Classic max-height Approach (All Browsers)
details > *:not(summary) {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease;
}
details[open] > *:not(summary) {
max-height: 500px; /* Must exceed actual content height */
}
Browser Support
| Feature | Chrome | Firefox | Safari | Edge |
|---|---|---|---|---|
<details> / <summary> | 12 | 49 | 6 | 79 |
toggle event | 12 | 49 | 6 | 79 |
name attribute (accordion) | 120 | 130 | 17.6 | 120 |
beforetoggle event | 114 | 130 | 17.2 | 114 |
::marker on summary | 86 | 68 | 11.1 | 86 |
Common Mistakes
Omitting the summary element: Without <summary>, browsers display a generic ‘Details’ label. Always provide descriptive summary text.
Using ::before to target the triangle: The disclosure triangle is ::marker, not ::before. Set list-style: none to remove it.
Setting display: none on summary: This makes the disclosure widget completely inaccessible — no toggle control is visible or operable.
Blocking the toggle with keydown: Use beforetoggle with event.preventDefault() instead — it cancels both click and keyboard toggle.
FAQ
How do I make only one accordion panel open at a time?
Use the name attribute: <details name="group">. All details elements with the same name form an exclusive group. Supported in Chrome 120+, Firefox 130+, Safari 17.6+.
How do I react to state changes?
Listen to the toggle event on the details element. It fires after every open/close. For modern browsers, the event has newState and oldState properties.
How do I prevent the details from closing?
Use the beforetoggle event and call event.preventDefault(). This cancels the state change before it happens.
Is the details element accessible?
Yes. The summary has an implicit role of ‘button’ with automatic aria-expanded. Screen readers handle it correctly in all major screen reader/browser combinations. Ensure your summary text is descriptive.
How do I animate the open/close?
Use CSS with @starting-style and display allow-discrete for modern browsers (Chrome 117+). For older browsers, use the max-height transition trick.