HTML details and summary Elements Explained
The HTML details and summary elements build native disclosure widgets and accordions with no JavaScript. How they work, how to style them, and pitfalls.
The <details> and <summary> elements are HTML’s built-in disclosure widget: a block of content that the user can expand and collapse by clicking a label. <details> wraps the whole thing, <summary> is the always-visible label, and everything else inside is hidden until the widget is opened. You get keyboard support, screen reader semantics, and open/close state without writing a line of JavaScript, which makes it the right starting point for FAQs, expandable sections, and simple accordions.
The basic pattern
<details>
<summary>What payment methods do you accept?</summary>
<p>We accept all major cards and bank transfers.</p>
</details>
That’s the entire component. The browser renders the summary with a disclosure triangle, hides the paragraph, and toggles it when the summary is clicked or activated with Enter or Space. The <summary> must be the first child of <details>; if you leave it out, the browser supplies a default label such as “Details.”
To render the widget open by default, add the boolean open attribute:
<details open>
<summary>Shipping</summary>
<p>Orders ship within two business days.</p>
</details>
The browser adds and removes open as the user toggles, so the attribute always reflects current state. That makes it a natural CSS hook.
Why it beats a hand-rolled toggle
A custom disclosure built from a <div> and a click handler needs a surprising amount of work to be correct: a focusable trigger, aria-expanded kept in sync, keyboard handling, and hidden content that is actually removed from the accessibility tree. <details> gives you all of that for free, and it works before any script loads, which is the essence of progressive enhancement.
It also cooperates with browser features a custom widget would miss. In supporting browsers, find-in-page can search the text inside a closed <details> and will open it automatically to reveal a match. Linking to an element inside a closed <details> via a fragment identifier can open it as well.
Like other semantic HTML elements, it tells assistive technology what the thing is. Screen readers announce the summary as a button-like control with an expanded or collapsed state.
Building an exclusive accordion with name
Historically, making only one section open at a time required JavaScript. Modern browsers support a name attribute on <details>: all elements sharing the same name form a group, and opening one closes the others.
<details name="faq">
<summary>Returns</summary>
<p>Return any item within 30 days.</p>
</details>
<details name="faq">
<summary>Warranty</summary>
<p>All products carry a one-year warranty.</p>
</details>
This mirrors how name groups radio buttons. In browsers that don’t support it yet, the attribute is simply ignored and each section toggles independently, which is a perfectly acceptable fallback.
Styling the marker
The disclosure triangle is a marker on the <summary>, which is displayed as list-item by default. You can restyle or remove it:
summary {
cursor: pointer;
list-style: none; /* removes the default marker in most browsers */
}
summary::-webkit-details-marker {
display: none; /* older WebKit-based browsers */
}
summary::after {
content: "+";
float: inline-end;
}
details[open] > summary::after {
content: "−";
}
The ::after pseudo-element approach, covered in more depth in CSS ::before and ::after explained, lets you draw any indicator you want. Changing summary’s display to flex or block also removes the default marker, which is convenient when you want a flex layout for an icon and label.
Always keep a visible focus style on the summary. If your reset strips outlines, restore them with :focus-visible.
Animating open and close
For years, <details> couldn’t be animated smoothly because its content snapped between hidden and shown. Two newer CSS features change that in supporting browsers:
- The
::details-contentpseudo-element targets the expandable part of the widget directly. interpolate-size: allow-keywordslets you transitionheightto and fromauto.
:root {
interpolate-size: allow-keywords;
}
details::details-content {
height: 0;
overflow: clip;
transition: height 0.25s ease, content-visibility 0.25s allow-discrete;
}
details[open]::details-content {
height: auto;
}
The allow-discrete keyword keeps the content visible until the closing transition finishes, a technique related to @starting-style. Wrap the transition in a prefers-reduced-motion check so users who opt out of motion get an instant toggle. Where these features aren’t supported, the widget still opens and closes; it just doesn’t animate.
Listening for changes in JavaScript
When you do need script, <details> fires a toggle event after its state changes:
document.querySelectorAll("details").forEach((el) => {
el.addEventListener("toggle", () => {
if (el.open) loadSectionContent(el);
});
});
The toggle event doesn’t bubble, so attach the listener to each element (or listen in the capture phase on a parent). This is a good place to lazy-load expensive content only when a section is first opened. To open or close programmatically, set el.open = true or false.
When not to use details
<details> is for disclosure: showing and hiding content inline. It isn’t the right tool for every expanding UI:
| Need | Better choice |
|---|---|
| Inline show/hide content, FAQs | <details> / <summary> |
| Modal that blocks the page | <dialog> with showModal() |
| Floating menu or tooltip over content | The Popover API |
| Tab interface with one visible panel | A tabs pattern with proper ARIA roles |
Also avoid putting interactive controls such as links or buttons inside <summary>. Nesting interactive elements inside another interactive element confuses both click handling and assistive technology. Keep the summary to text, and perhaps an icon.
The takeaway
<details> and <summary> give you an accessible, keyboard-friendly disclosure widget with zero JavaScript. Use the open attribute as your state and styling hook, the name attribute for exclusive accordions, list-style or ::after to customize the marker, and ::details-content with interpolate-size for smooth animation where supported. Reach for <dialog> or popovers when the content should float above the page instead of expanding in place.
Tagged
Keep reading
Takina · · 4 min read CSS pointer-events Explained: Click-Through and Its Traps
CSS pointer-events: none makes an element ignore mouse, touch, and pen input so clicks pass through. How it works, good uses, and why it doesn't disable.
Takina · · 4 min read CSS @starting-style Explained: Entry Transitions in CSS
CSS @starting-style defines an element's before-state on first render, so transitions can animate elements as they appear, even from display: none.
Takina · · 4 min read Container Query Units: cqw, cqh, and cqi Explained
Container query units like cqw and cqi size elements relative to their containing element, not the viewport — here's how and when to use them.