Accordion Item
Each collapsible section. Pass a title prop for the trigger text; children become the expandable body.
<details
data-acc="item"
class="acc-item border-b border-gray-200 dark:border-gray-700"
>
<summary
class="acc-trigger flex w-full cursor-pointer items-center justify-between py-4 text-left font-medium transition-colors hover:underline"
>
<span>{{ title }}</span>
<svg
class="acc-chevron h-4 w-4 shrink-0 text-gray-500 dark:text-gray-400"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7" />
</svg>
</summary>
<div class="acc-body">
<div class="acc-content pb-4 text-gray-600 dark:text-gray-300">
<yield />
</div>
</div>
</details>
.acc-item summary {
list-style: none;
}
.acc-item summary::-webkit-details-marker {
display: none;
}
.acc-item summary::marker {
display: none;
content: "";
}
.acc-chevron {
transition: transform 200ms ease;
}
details[open] > summary .acc-chevron {
transform: rotate(180deg);
}
.acc-body {
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows 200ms ease-out;
}
details[open] > .acc-body {
grid-template-rows: 1fr;
}
.acc-content {
overflow: hidden;
}
Accordion
Wrapper for accordion items. Set type="multiple" to allow several items open at once; defaults to single (opening one closes the others).
<div data-acc-type="{{ type: single }}">
<yield />
</div>
document.addEventListener("DOMContentLoaded", function () {
document
.querySelectorAll('[data-tag="accordion"]:not([data-acc-init])')
.forEach(function (wrapper) {
wrapper.setAttribute("data-acc-init", "")
var type = wrapper.getAttribute("data-acc-type") || "single"
var items = wrapper.querySelectorAll("details[data-acc='item']")
var triggers = []
items.forEach(function (detail) {
var uid = "acc-" + Math.random().toString(36).substr(2, 8)
var summary = detail.querySelector("summary")
var body = detail.querySelector(".acc-body")
if (!summary || !body) return
summary.setAttribute("id", uid + "-t")
summary.setAttribute("aria-controls", uid + "-p")
body.setAttribute("id", uid + "-p")
body.setAttribute("role", "region")
body.setAttribute("aria-labelledby", uid + "-t")
triggers.push(summary)
})
if (type === "single") {
items.forEach(function (detail) {
detail.addEventListener("toggle", function () {
if (!detail.open) return
items.forEach(function (other) {
if (other !== detail && other.open) {
other.open = false
}
})
})
})
}
wrapper.addEventListener("keydown", function (e) {
var idx = triggers.indexOf(document.activeElement)
if (idx === -1) return
var next = -1
if (e.key === "ArrowDown") next = (idx + 1) % triggers.length
else if (e.key === "ArrowUp")
next = (idx - 1 + triggers.length) % triggers.length
else if (e.key === "Home") next = 0
else if (e.key === "End") next = triggers.length - 1
if (next !== -1) {
e.preventDefault()
triggers[next].focus()
}
})
})
})