Which CSS accordion method should you use?
Native details and summary, with a shared name attribute when only one panel should stay open at a time.
Radio inputs, checkboxes and :target links exist because details used to be unreliable. These days they cost you semantics and give very little back.
In practice, a CSS accordion here means any stack of expandable panels that toggles without script handlers. A button wired with aria-expanded needs JavaScript to flip its state, so it falls outside this comparison.
The details interface itself is old. MDN’s browser compatibility data dates it to Chrome 12, Firefox 49 and Safari 6.
| Method | Semantics | Panels open | Browser floor |
|---|
| details and summary | Native disclosure widget | Several at once | Every current browser |
| details with name | Native disclosure widget | One at a time | Chrome 120, Safari 17.2, Firefox 130 |
| Radio inputs | Radio button, no expanded state | One at a time | Selector support only |
| Checkbox | Checkbox, no expanded state | Several at once | Selector support only |
| :target links | Plain links and URL fragments | One at a time | Selector support only |
If several panels can stay open, plain details is enough. For one panel at a time in current browsers, add name. And if you need one at a time with no script on browsers older than the versions in the table, radio inputs will do it, though you accept the accessibility cost.
Checkbox and :target accordions never beat details. Skip them in new builds.
How to build a CSS accordion with details and summary
Wrap each item in a details element and put the visible label in a summary. The browser takes care of the toggle and the open state, and keyboard operation comes along with it.
Both elements are standard HTML, so the accordion still works with the stylesheet switched off.
- Write the markup, one details per item, with the summary first and the content after it. MDN’s reference says summary must be the first child, otherwise the browser inserts a default label (typically “Details”). Add the open attribute to any item that should start expanded.
- Style the summary. Set cursor, padding, a border between items, and visible hover and focus states.
- Remove the default triangle by setting list-style to none, and add summary::-webkit-details-marker with display none for Safari, as MDN documents.
- Add a custom icon. Draw it with summary::after and rotate it in a rule scoped to details[open].
- Style the panel itself. Target details::details-content for padding and background, wrapped in an @supports selector(::details-content) block.
The @supports wrapper matters because ::details-content shipped late. Per the Web Platform DX web-features dataset, Chrome added it in 131 (2024-11-12), Safari in 18.4 (2025-03-31) and Firefox in 143 (2025-09-16), which made it Baseline Newly Available on 2025-09-16.
In browsers without it, the accordion still opens and closes. It just keeps the default panel styling.
One catch from MDN is that the role assigned to summary varies across browsers. Test the widget on more than one platform before calling it accessible.
Exclusive accordions with the details name attribute
Give several details elements the same name value and the browser closes the open one whenever another opens.
Chrome for Developers and the Web Platform DX web-features dataset (checked October 2026) list these support dates for the name attribute:
- Chrome and Chrome Android 120, released 2023-12-05
- Safari and Safari on iOS 17.2, released 2023-12-11
- Firefox 130, released 2024-09-03
- Baseline status: Newly Available since 2024-09-03, expected to reach Widely Available on 2027-03-03
Firefox was the last engine to ship it, so the practical floor is September 2024. Check the Can I use table before dropping a fallback for older devices.
The name groups elements, not their DOM order, so grouped panels can sit in different containers. A new name value starts a new group, which lets one page hold several exclusive accordions.
For older browsers, Chrome for Developers publishes a polyfill built on the toggle event. When a named details opens, the script removes the open attribute from the others in the group.
Browsers that never fire toggle skip the script entirely. The same page calls that acceptable progressive enhancement.
The documentation also adds a caution. Exclusive accordions save space, but people may have to open many items to find an answer, and anyone comparing two items at once gets frustrated.
Default to several panels open. Use name when each answer is long and independent, like a support FAQ where readers want one answer and nothing else.
How do checkbox, radio and :target accordions work?
They keep the open state outside a details element, either in a checked input or in the URL fragment, and a selector reveals the matching panel.
Checkbox accordions
A hidden checkbox sits before a label and a content block. Clicking the label toggles the checkbox, and the :checked pseudo-class plus a sibling combinator shows the panel.
Codrops published this pattern in 2012 and it spread because it needs no script. Each checkbox is independent, so several panels stay open together.
Radio accordions
Swap the checkbox for a radio input, give every item the same name, and the browser allows one open panel. That is the same rule the details name attribute now provides natively.
The upside is single-open behavior in browsers that predate the name attribute. The downside is that a radio can’t be unchecked by clicking it again, so the open panel has no built-in way to close. It also announces itself as a radio button, not as an expandable section.
:target accordions
Each header links to a section ID, and the panel matching :target opens. GrahamTheDev’s DEV Community walkthrough pairs this with a grid-rows trick for animated height.
- Every click changes the URL fragment, so the Back button steps through panel states.
- Only one panel can open, because a page has a single :target.
- Collapsing needs a second link pointing at a different fragment.
- The page also scrolls toward the target on each click.
Where these methods break
Wikimedia’s Phabricator task T243126 records the trade-off. The checkbox hack spread across skins and Vector menus to cut JavaScript, and the proposed accessibility fix (updating aria-expanded by script) brings JavaScript back.
The same discussion notes that the checkbox keeps its inherent role, because ARIA should not override it with role=”button”.
My take is to skip all of them wherever the name attribute works. Radio inputs only make sense on script-free pages that must support browsers older than Chrome 120, Safari 17.2 or Firefox 130.
Animating CSS accordion height without JavaScript
Animating panel height in plain CSS comes down to interpolate-size with ::details-content, a grid-template-rows switch from 0fr to 1fr, or a max-height transition. On a native details element, only the first animates both opening and closing.
The shared problem is that height: auto is not a value the browser can interpolate by default. Each technique is a different workaround for it.
Support splits along engine lines. The name attribute and ::details-content ship in Chrome, Safari and Firefox, but interpolate-size does not, so animated auto height is a Chromium feature for now.
::details-content with interpolate-size
Opt in with interpolate-size: allow-keywords on :root. Then start details::details-content at height 0 with overflow clip, transition height and content-visibility (with the allow-discrete keyword), and set height auto under details[open].
It animates opening and closing on plain details markup, with no wrapper div and no guessed heights. The catch is that auto-height animation only runs where interpolate-size exists. The pseudo-element also defaults to display block, where it used to behave like display contents, so children that rely on height 100% can break.
Chrome for Developers documents a workaround for that last problem: display contents on the open pseudo-element. It can’t be combined with a height animation, because a display contents box has no height to animate.
Per the Web Platform DX web-features dataset, interpolate-size (specified in CSS Values and Units Level 5 by the CSS Working Group) shipped in Chrome 129 on 2024-09-17 and Edge 129 on 2024-09-19. Firefox (bug 1945962, vendor position positive) and Safari (WebKit bug 295132, vendor position support) lack it as of October 2026.
Gate the animation with @supports (interpolate-size: allow-keywords). Browsers outside the gate keep a working accordion without motion, and for most content that beats a fixed-height box with a scrollbar.
grid-template-rows from 0fr to 1fr
DockYard’s October 2024 write-up calls CSS Grid the cleanest browser-safe route to intrinsic-size animation.
The wrapper uses display grid with grid-template-rows 0fr and a transition on that property. The open state switches it to 1fr, and the inner element needs overflow hidden so the collapsed row clips its content.
One quirk. Between 0 and 1fr, Grid can briefly create a second row and the animation stutters. DockYard fixes it with grid-row: 1 / span 2 on the container.
It works in every major browser, takes content of any height and needs no script. But on a native details element the wrapper animates only on opening. Chrome for Developers tested a wrapper div and saw no closing animation.
The pattern stays the standard choice for button-driven accordions, where an attribute such as data-open flips the state, as in DockYard’s example.
max-height transitions
Skip it in new builds.
A max-height transition runs between 0 and the cap you declare, not the real content height. Put a 1000px cap on a 200px panel and most of the duration passes with nothing visibly moving, and the close lags.
| Pros | Cons |
|---|
| Works in every browser | Needs a guessed upper bound |
| One property to learn | Timing drifts from the real height |
| Fine for fixed-height panels | Taller content gets clipped |
It still suits panels with a known height. Variable copy needs one of the two techniques above.
Respecting reduced motion
Wrap every transition in a prefers-reduced-motion check. WCAG 2.3.3 Animation from Interactions (Level AAA) asks that motion triggered by interaction can be turned off unless it carries information, and W3C’s Technique C39 shows the media query route.
- Set the duration through a CSS variable and zero it under prefers-reduced-motion: reduce, as the DEV Community accordion walkthrough does.
- Or write static styles first and add motion only under prefers-reduced-motion: no-preference.
Chrome for Developers leaves the check out of its ::details-content demos for brevity and says production code should include it.
What makes a CSS accordion accessible?
Each header needs to be a real control with an announced expanded or collapsed state, and every action has to work from the keyboard alone.
Native details and summary supply that behavior by default. W3C’s WCAG 2.2 text notes that standard HTML controls already meet Success Criterion 4.1.2 (Name, Role, Value, Level A) when used according to specification, so the burden falls on components you script or fake.
Semantics a custom accordion must add
The W3C ARIA Authoring Practices Guide sets the baseline for anything built from buttons and panels, using ARIA states and properties. The title goes inside an element with role button, aria-expanded is true while the panel is visible, and aria-controls holds the ID of the panel content. If the accordion doesn’t allow collapsing, an open header also gets aria-disabled set to true.
Keyboard operation
| Key | Action | Status |
|---|
| Enter or Space on a header | Expands a collapsed panel, collapses an open one if allowed | Required |
| Tab and Shift+Tab | Move through all focusable elements, panel contents included | Required |
| Down and Up arrows | Move to the next or previous header | Optional |
| Home and End | Move to the first or last header | Optional |
Native summary covers the two required rows. The optional arrow-key movement between headers is absent from details, and adding it takes script.
A checkbox or radio accordion whose input uses display none never receives focus, so keyboard users can’t reach the header at all. Hide the input with a visually-hidden pattern instead, and tie the label to it with the for attribute.
Focus visibility
WCAG 2.4.7 Focus Visible (Level AA) means summary and label need a visible focus indicator. Removing the default outline without a replacement fails it.
WCAG 2.2 added 2.4.11 Focus Not Obscured (Minimum) (Level AA). A sticky navigation bar that covers a focused summary after the page scrolls runs into it. A scroll-padding-top on the root keeps focused headers clear of the bar.
Testing the result
Deque’s March 2021 study of its axe-powered automated testing found it covered 57 percent of accessibility issues by volume, above the 20 to 30 percent commonly assumed. It is the vendor’s own tooling and data set, so treat the figure as a best case for Lighthouse or axe DevTools.
Run both for the cheap catches, then test by hand. Tab to each header and toggle it with Enter and Space, checking that the focus ring stays visible and unobscured once a panel opens. Then listen with NVDA, JAWS or VoiceOver for the announced state.
Searchability and layout shift in collapsed panels
A collapsed panel built carelessly costs two things: text that find-in-page can’t reach, and height changes that move the page.
Find-in-page and fragment links
Native details elements are searchable in Chromium browsers and in Firefox 148 or later, and partly in Safari 26.2. Per the HTML Standard as summarized by Chrome for Developers, a closed details expands automatically when the browser scrolls to hidden content after find-in-page, a text fragment link or an element fragment link.
Custom panels need hidden=until-found. Chrome for Developers documents it as available from Chrome 102 (May 2022), with a beforematch event that fires just before the browser reveals the section.
The documentation attaches a few caveats. The attribute applies content-visibility hidden, not display none, so the element keeps its box and borders still render. Put borders on a nested element. Panels also have to open without find-in-page, since not every visitor searches.
Browsers without support fall back to plain hidden content. Chrome for Developers suggests detecting them by checking for onbeforematch on HTMLElement.prototype, but Safari 26.2 added the beforematch event with only partial support for the attribute value (MDN’s compatibility data notes it does not scroll correctly to the match), so the check passes in Safari. Test in Safari instead of relying on it alone.
GOV.UK Frontend’s accordion adopted the attribute in v4.5.0, so the browser’s own search can open its sections.
Support has moved since then. The v4.5.0 release notes (January 2023) limited it to recent Chrome, Edge and Samsung Internet. Firefox has now shipped hidden=until-found (Firefox 148, released 2026-02-24), while Safari 26.2 supports it only partially, so the Web Platform DX dataset still lists Safari as missing it. Auto-expanding closed details elements on find-in-page and hidden=until-found were both part of the details element focus area in Interop 2025.
Check the browsers you target before relying on it.
Layout shift
web.dev’s CLS guide sets a good Cumulative Layout Shift score at 0.1 or less, measured at the 75th percentile of page loads across mobile and desktop. Each shift scores impact fraction times distance fraction, and CLS is one of the Core Web Vitals.
The same guide says a shift only hurts when the user does not expect it. A tap on a summary is an expected cause, so the risk sits elsewhere. A panel opened by script after first paint, from a URL hash for example, pushes everything below it down, so set the open attribute in the HTML instead. Late-loading content or images without declared dimensions inside an open panel change its height too.
Height animation also reflows the content below on every frame, while opacity and transform do not. Keep durations short on long pages.
When a CSS accordion is the wrong choice
Skip the accordion when the hidden text is what most visitors came to read, or when the page needs controls CSS can’t provide.
It’s also a poor fit when panels hold a single short sentence, since the header then costs more than the content, or when the content must be visible on first load. Need open-all and close-all buttons, analytics on every toggle, or arrow-key movement between headers? Details fires a toggle event, but wiring any of that takes script. And if the sections are peer views of one subject, tabs are the better pattern.
Accordion or tabs?
| Dimension | Accordion | Tabs |
|---|
| Keyboard model | Tab through headers, arrows optional | Arrow keys move within the tab list |
| Panels visible | One, several or all | One at a time |
| Fits | Long lists of independent sections | Peer views of one subject |
The W3C APG gives each pattern its own keyboard model. A layout that swaps tabs for an accordion at a breakpoint must implement both correctly, so pick one unless the design demands the switch.
When a framework fits better
The accordion component in Bootstrap runs on the framework’s collapse plugin. Buttons carry data-bs-toggle=”collapse” and data-bs-target with aria-expanded and aria-controls set in the markup, and data-bs-parent groups panels into single-open behavior (Bootstrap 5.3 documentation).
The cost is loading Bootstrap’s JavaScript bundle for a widget that details handles natively.
Tailwind CSS goes the other way. An accordion styled with Tailwind utilities adds classes to native markup and no behavior of its own.
I’d use native details when styling is the only gap, and a framework accordion when its JavaScript already ships with the site. If the content should simply be visible, use neither.
CSS Accordion FAQ
Can you nest one accordion inside another?
Yes. A details element can sit inside another panel, and each keeps its own open state.
Give the inner group a different name value, because a new name value starts a separate exclusive group (Chrome for Developers).
Can search engines read text inside a closed panel?
They can. Closed panel text stays in the page markup, and Chrome for Developers says hidden=until-found makes collapsed content accessible to search engines, with Google Search forming links that scroll to the revealed fragment.
The documentation makes no claim about ranking weight.
How do I link straight to one accordion panel?
Put an ID on an element inside the panel and link to it. In Chromium browsers and Firefox 148 or later (partially in Safari 26.2), a closed details expands when element fragment navigation, find-in-page or a text fragment link scrolls to hidden content, per the HTML Standard as summarized by Chrome for Developers.
Custom panels need hidden=until-found for the same behavior.
What a Production-Ready CSS Accordion Needs
Build on native details and summary, and treat every enhancement as removable. If animation or single-open grouping fails in a browser, the panels still open and close.
Start with plain details, visible focus and the open state set in HTML. Add a shared name value only where one panel at a time helps readers, and put animation behind @supports with a reduced-motion rule. Each layer fails safely, so the one before it keeps working.
The trade-off is that Firefox and Safari visitors get an instant toggle where Chromium visitors get motion.
The fallback layer disappears the day Firefox and Safari both ship interpolate-size, since one animation rule would then serve all three engines. Position verified October 2026.
When sections are peers instead of a stack, tabs built in CSS are the next component to build.