Skip to content

Details and Summary

The <details> and <summary> elements create native disclosure widgets (accordions) that work without JavaScript.

<div class="accordion">
<div class="accordion-item">
<button class="accordion-header" onclick="toggleAccordion(this)">
Section 1
<span class="icon"></span>
</button>
<div class="accordion-content">
<p>Content for section 1...</p>
</div>
</div>
<div class="accordion-item">
<button class="accordion-header" onclick="toggleAccordion(this)">
Section 2
<span class="icon"></span>
</button>
<div class="accordion-content">
<p>Content for section 2...</p>
</div>
</div>
</div>
<style>
.accordion-content {
display: none;
padding: 1rem;
}
.accordion-item.open .accordion-content {
display: block;
}
.accordion-item.open .icon {
transform: rotate(180deg);
}
</style>
<script>
function toggleAccordion(button) {
const item = button.parentElement;
item.classList.toggle('open');
}
</script>

Problems:

  • Requires JavaScript for basic functionality
  • Must manage open/close state manually
  • Need to implement keyboard accessibility
  • Icon rotation handled separately
<details>
<summary>Section 1</summary>
<p>Content for section 1...</p>
</details>
<details>
<summary>Section 2</summary>
<p>Content for section 2...</p>
</details>

Benefits:

  • Zero JavaScript required
  • Keyboard accessible by default
  • Browser manages open/close state
  • Works even if CSS fails to load

Use the open attribute to start expanded:

<details open>
<summary>Expanded by default</summary>
<p>This content is visible initially.</p>
</details>
details {
border: 1px solid #ccc;
border-radius: 4px;
padding: 0.5rem;
margin-bottom: 0.5rem;
}
summary {
cursor: pointer;
font-weight: bold;
padding: 0.5rem;
}
/* Remove default marker */
summary {
list-style: none;
}
summary::-webkit-details-marker {
display: none;
}
/* Custom marker */
summary::before {
content: '';
display: inline-block;
transition: transform 0.2s;
}
details[open] summary::before {
transform: rotate(90deg);
}
/* Animate content (limited support) */
details[open] > *:not(summary) {
animation: fade-in 0.3s;
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}

Use the name attribute to group details elements:

<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 opens, others in the same group automatically close.

Listen for toggle events:

const details = document.querySelector('details');
details.addEventListener('toggle', () => {
if (details.open) {
console.log('Details opened');
} else {
console.log('Details closed');
}
});

Programmatic control:

// Open
details.open = true;
// or
details.setAttribute('open', '');
// Close
details.open = false;
// or
details.removeAttribute('open');

Details can be nested for hierarchical content:

<details>
<summary>Category</summary>
<details>
<summary>Subcategory 1</summary>
<p>Content...</p>
</details>
<details>
<summary>Subcategory 2</summary>
<p>Content...</p>
</details>
</details>

Supported in all modern browsers. The name attribute for exclusive accordions is supported in Chrome 120+, Firefox 130+, Safari 17.2+.

  • Keyboard accessible: Enter/Space toggles, Tab navigates
  • Announced as expandable by screen readers
  • Summary is focusable by default
  • State change announced automatically

Best practices:

  • Keep summary text concise and descriptive
  • Don’t put interactive elements inside summary
  • Ensure content is meaningful when expanded

Use <details> for:

  • FAQs
  • Collapsible sections
  • Show/hide additional information
  • Code examples with expandable explanation
  • Table of contents with subsections

Consider alternatives for:

  • Tab interfaces (use tab pattern with ARIA)
  • Complex multi-panel layouts
  • Content that should always be visible to search engines