Smooth accordion
FAQ panels open and close with an animated height, using the grid-template-rows 0fr to 1fr trick, plus a rotating chevron.
CSSJavaScriptIntermediate
<div class="acc">
<div class="item open"><button class="head" aria-expanded="true" aria-controls="a1">What is this?<span class="chev"></span></button><div class="panel" id="a1"><div><p>A smooth accordion that animates to the exact height of its content.</p></div></div></div>
<div class="item"><button class="head" aria-expanded="false" aria-controls="a2">Does it need JavaScript?<span class="chev"></span></button><div class="panel" id="a2"><div><p>Only a tiny script to toggle a class and the aria-expanded state. The animation itself is CSS.</p></div></div></div>
<div class="item"><button class="head" aria-expanded="false" aria-controls="a3">Can several be open?<span class="chev"></span></button><div class="panel" id="a3"><div><p>Yes. Each item toggles on its own. Close the others in the script if you want one at a time.</p></div></div></div>
</div>
.acc{width:min(400px,90vw);display:grid;gap:10px}
.item{border:1px solid #4a2e2a;border-radius:12px;background:#1f0f0e;overflow:hidden}
.head{width:100%;display:flex;justify-content:space-between;align-items:center;gap:12px;font:inherit;font-weight:600;color:inherit;background:none;border:0;padding:16px 18px;cursor:pointer;text-align:left}
.chev{width:12px;height:12px;border-right:2px solid #f08a5d;border-bottom:2px solid #f08a5d;transform:rotate(45deg) translate(-2px,-2px);transition:transform .4s cubic-bezier(.2,.8,.2,1)}
.panel{display:grid;grid-template-rows:0fr;transition:grid-template-rows .4s cubic-bezier(.2,.8,.2,1);visibility:hidden}
.panel>div{overflow:hidden;min-height:0}
.panel p{margin:0;padding:0 18px 18px;color:#cfc3c1}
.item.open .panel{grid-template-rows:1fr;visibility:visible}
.item.open .chev{transform:rotate(225deg) translate(-2px,-2px)}
.item .panel{transition:grid-template-rows .4s cubic-bezier(.2,.8,.2,1),visibility 0s .4s}
.item.open .panel{transition:grid-template-rows .4s cubic-bezier(.2,.8,.2,1),visibility 0s}
document.querySelectorAll('.head').forEach(btn => {
btn.addEventListener('click', () => {
const item = btn.parentElement;
const open = item.classList.toggle('open');
btn.setAttribute('aria-expanded', open);
});
});
<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Smooth accordion</title>
<style>
*{box-sizing:border-box}
html,body{margin:0;height:100%}
body{display:grid;place-items:center;background:#140807;color:#fffffc;font:16px/1.5 system-ui,-apple-system,"Segoe UI",sans-serif}
.acc{width:min(400px,90vw);display:grid;gap:10px}
.item{border:1px solid #4a2e2a;border-radius:12px;background:#1f0f0e;overflow:hidden}
.head{width:100%;display:flex;justify-content:space-between;align-items:center;gap:12px;font:inherit;font-weight:600;color:inherit;background:none;border:0;padding:16px 18px;cursor:pointer;text-align:left}
.chev{width:12px;height:12px;border-right:2px solid #f08a5d;border-bottom:2px solid #f08a5d;transform:rotate(45deg) translate(-2px,-2px);transition:transform .4s cubic-bezier(.2,.8,.2,1)}
.panel{display:grid;grid-template-rows:0fr;transition:grid-template-rows .4s cubic-bezier(.2,.8,.2,1);visibility:hidden}
.panel>div{overflow:hidden;min-height:0}
.panel p{margin:0;padding:0 18px 18px;color:#cfc3c1}
.item.open .panel{grid-template-rows:1fr;visibility:visible}
.item.open .chev{transform:rotate(225deg) translate(-2px,-2px)}
.item .panel{transition:grid-template-rows .4s cubic-bezier(.2,.8,.2,1),visibility 0s .4s}
.item.open .panel{transition:grid-template-rows .4s cubic-bezier(.2,.8,.2,1),visibility 0s}
</style></head>
<body>
<div class="acc">
<div class="item open"><button class="head" aria-expanded="true" aria-controls="a1">What is this?<span class="chev"></span></button><div class="panel" id="a1"><div><p>A smooth accordion that animates to the exact height of its content.</p></div></div></div>
<div class="item"><button class="head" aria-expanded="false" aria-controls="a2">Does it need JavaScript?<span class="chev"></span></button><div class="panel" id="a2"><div><p>Only a tiny script to toggle a class and the aria-expanded state. The animation itself is CSS.</p></div></div></div>
<div class="item"><button class="head" aria-expanded="false" aria-controls="a3">Can several be open?<span class="chev"></span></button><div class="panel" id="a3"><div><p>Yes. Each item toggles on its own. Close the others in the script if you want one at a time.</p></div></div></div>
</div>
<script>
document.querySelectorAll('.head').forEach(btn => {
btn.addEventListener('click', () => {
const item = btn.parentElement;
const open = item.classList.toggle('open');
btn.setAttribute('aria-expanded', open);
});
});
</script>
</body></html>
Good for
FAQs, filters and any expandable section.
Watch out for
Use real buttons with aria-expanded and aria-controls. Hidden content should also be hidden from the tab order (visibility hidden when closed).
Accessibility
Wrap motion in @media (prefers-reduced-motion: reduce) and switch it off or shorten it for people who ask for less movement.