Accordion
Collapsible items built on the browser's
<details>. No JS needed.
Usage
Each item is a <details> with a
<summary>, which gets its own arrow. Give them
all the same name so only one opens at a time:
opening one closes the other. Add open to the item
that starts open.
How long is shipping?
2-3 business days within the US.
Can I return items?
Yes, within 30 days of delivery.
Which payment methods do you accept?
Card, PayPal and bank transfer.
<div class="accordion">
<details name="faq" open>
<summary>How long is shipping?</summary>
<p>2-3 business days within the US.</p>
</details>
<details name="faq">
<summary>Can I return items?</summary>
<p>Yes, within 30 days of delivery.</p>
</details>
<details name="faq">
<summary>Which payment methods do you accept?</summary>
<p>Card, PayPal and bank transfer.</p>
</details>
</div>
Several open at once
Without name, each item opens and closes on its own.
Product details
100% cotton T-shirt, $24.00
Shipping
Free shipping on orders over $50.
Care
Machine wash cold.
<div class="accordion">
<details open>
<summary>Product details</summary>
<p>100% cotton T-shirt, $24.00</p>
</details>
<details open>
<summary>Shipping</summary>
<p>Free shipping on orders over $50.</p>
</details>
<details>
<summary>Care</summary>
<p>Machine wash cold.</p>
</details>
</div>
A disabled item
Add aria-disabled="true" to the
<details> and tabindex="-1" to the
<summary>. The item turns grey and cannot be
clicked or reached with Tab.
Credit card
Visa, Mastercard, JCB
Cash on delivery (not available)
Not supported yet.
<div class="accordion">
<details name="pay">
<summary>Credit card</summary>
<p>Visa, Mastercard, JCB</p>
</details>
<details name="pay" aria-disabled="true">
<summary tabindex="-1">Cash on delivery (not available)</summary>
<p>Not supported yet.</p>
</details>
</div>
Listen for toggle
toggle is a browser event that a
<details> fires each time it opens or closes.
It does not bubble, so listen in the capture phase on the
container (or on each details). Read the state from
event.newState or details.open. To open
or close one in code, set details.open = true.
UPS Ground
1-2 days, $9.00
USPS
2-4 days, $6.00
Nothing opened yet
<div class="stack" data-gap="sm">
<div class="accordion" id="acc-events">
<details name="ship">
<summary>UPS Ground</summary>
<p>1-2 days, $9.00</p>
</details>
<details name="ship">
<summary>USPS</summary>
<p>2-4 days, $6.00</p>
</details>
</div>
<p id="acc-events-output">Nothing opened yet</p>
<div>
<button type="button" class="btn" data-size="sm" id="acc-events-open">
Open the first in code
</button>
</div>
</div>
<script type="module">
const accordion = document.getElementById("acc-events");
const output = document.getElementById("acc-events-output");
// toggle does not bubble: listen in the capture phase.
accordion.addEventListener(
"toggle",
() => {
const open = accordion.querySelector("details[open] > summary");
output.textContent = open ? `Open: ${open.textContent}` : "All closed";
},
{ capture: true },
);
document.getElementById("acc-events-open").addEventListener("click", () => {
accordion.querySelector("details").open = true;
});
</script>
API
Accordion has no JS of its own. Use the properties and events of
<details> directly.
| Property / event / key | Values / detail | Description |
|---|---|---|
details.open |
true, false |
Read it, or set it to open or close the item in code |
toggle |
event.oldState, event.newState:
"open" / "closed"
|
Fired by details after it opens or closes.
Does not bubble (listen with
{ capture: true } on .accordion)
|
| Enter, Space | On a summary: open or close the item (native browser behavior) |
Attributes
| Attribute / element | Values | Description |
|---|---|---|
.accordion |
Bordered box with a divider between items | |
> details |
One item | |
details[name] |
Text; none = several can be open (default) | Same name across the group: one open at a time (the browser closes the others) |
details[open] |
Present / absent; absent = closed (default) | The item is open. The browser adds and removes it. Set it in markup to start open. |
details[aria-disabled] |
true |
Disabled: the summary is grey and cannot be clicked. Also
add tabindex="-1" to the summary.
|
summary |
Heading with an arrow on the right that turns when open | |
details > :not(summary) |
Content (p, div,
section ...) in grey, indented to line up
with the heading
|