On this page
Collapse
Show and hide a panel with <details> and
<summary> — no Collapse plugin. The trigger and
the panel must be parent and child.
| What runs it | JavaScript | |
|---|---|---|
| Bootstrap 5.3 | Collapse plugin | 5.6 KB gzip |
| This repo | details and summary | 0 KB |
| Avoid |
|
|---|
How it works
-
The widget is
details.collapse. Addopento start expanded (replaces.collapse.show). -
The trigger is the
<summary>, typically with.btn. It has to live inside the<details>— there is nodata-bs-target/hrefpointing at a distant id. -
The browser exposes expanded/collapsed on the summary (replaces
aria-expandedtoggling from JavaScript). You can still addaria-controlsif you give the panel anid, but it is not required. -
::details-contentis the panel, and height (or width with.collapse-horizontal) transitions on it. That transition also needsinterpolate-size: allow-keywords, which only Chromium has, so Firefox and Safari snap the panel open. Do not put padding on.collapseitself — keep it on an inner wrapper such as.card-body, same as Bootstrap. -
The same
nameon several<details>elements makes the group exclusive (replacesdata-bs-parent). See Accordion for that pattern. -
<summary>is a button-like control, not an<a href>. -
Closed panels stay in the document, so Find in page can match
them (try archaeopteryx in the first example).
Do not put
hiddenordisplay: noneon the inner wrapper. For a custom region that is not a<details>panel, usehidden="until-found"with padding on a nested child. Bootstrap’s reboot would otherwise forcedisplay: noneand break that attribute.
Example
Click the summary to show and hide the panel. .collapse hides content; [open] shows it; ::details-content handles the transition.
#collapseExample. A
<details> element has a single
<summary>, so there is one trigger, and it
must sit next to the panel.
Toggle collapse
<details class="collapse">
<summary class="btn btn-primary mb-2">Toggle collapse</summary>
<div class="card card-body">
Some placeholder content for the collapse component. This panel is hidden by default but revealed when the user activates the relevant trigger. Find in page can still match it: the token is <strong>archaeopteryx</strong>.
</div>
</details> Horizontal
Add .collapse-horizontal to transition width instead of height, and set a width on the immediate child of the panel.
Toggle width collapse
<div style="min-height: 120px;">
<details class="collapse collapse-horizontal">
<summary class="btn btn-primary mb-2">Toggle width collapse</summary>
<div class="card card-body" style="width: 300px;">
This is some placeholder content for a horizontal collapse. It’s hidden by default and shown when triggered.
</div>
</details>
</div> Multiple toggles and targets
Each panel has its own summary. Independent details elements can be open at the same time.
data-bs-target selector and JavaScript. Native HTML
also has no “Toggle both” button.
Toggle first element
Toggle second element
<div class="row">
<div class="col">
<details class="collapse">
<summary class="btn btn-primary mb-2">Toggle first element</summary>
<div class="card card-body">
Some placeholder content for the first collapse component of this multi-collapse example. This panel is hidden by default but revealed when the user activates the relevant trigger.
</div>
</details>
</div>
<div class="col">
<details class="collapse">
<summary class="btn btn-primary mb-2">Toggle second element</summary>
<div class="card card-body">
Some placeholder content for the second collapse component of this multi-collapse example. This panel is hidden by default but revealed when the user activates the relevant trigger.
</div>
</details>
</div>
</div> Find in page
hidden="until-found" hides a custom region until Find in page matches it. Collapse panels themselves stay on details — do not put hidden on the card.
Search this page for coelacanth.
<p>Search this page for <strong>coelacanth</strong>.</p>
<div hidden="until-found">
<div class="card card-body">
Find in page revealed this region. The token is <strong>coelacanth</strong>.
</div>
</div> Accessibility
<summary> is the control. The browser maps it to a disclosure, including Enter and Space. A hidden checkbox plus label is a checkbox, not a disclosure; that pattern is documented under Checkbox hack and is not used here.
Closed panels stay in the document. Find in page can match them. Do not add display: none on the inner wrapper.
Browser support
| No script | Polyfill | No fallback |
|---|---|---|
details / summary. Trigger and panel must be parent/child. | None | Same interpolate-size limit as the accordion. .collapse-horizontal animates to max-content and depends on it too. The panel opens and closes everywhere. |
Checkbox hack
A hidden checkbox plus a <label for> can toggle
a sibling with :checked. That would let the trigger
sit beside the panel (still not across the document), and several
labels could share one id.
It is not worth a second Collapse variant, mainly for WCAG:
- 4.1.2 Name, Role, Value. The control is exposed
as a checkbox (“checked / not checked”), not as a disclosure
(“expanded / collapsed”).
<details>/<summary>already maps to that disclosure role. Fixing the checkbox withrole="button"and livearia-expandedneeds JavaScript, which is what the hack is meant to avoid. - 2.4.3 Focus Order / 2.4.7 Focus Visible. The real tab stop is the visually hidden input, so focus rings and AT cursor land off the visible button.
- 1.3.1 Info and Relationships. A form control used as chrome is announced in forms mode and can be submitted with a surrounding form.
- 2.1.1 Keyboard. A checkbox toggles with Space. A summary (and Bootstrap’s button trigger) also accepts Enter.
Keep remote or one-to-many toggles for JavaScript. For native
collapse, <details> is the accessible control.