Pure TailwindMIT licensed

Tailwind Material Design Expansion Panels and Accordion

Expansion panels on <details>, with the one thing the checkbox-hack version could never have: name=&quot;…&quot; makes the accordion <b>exclusive</b> natively, so opening one panel closes the others with no script and no radio hack. Tailwind styles the whole thing through open: and a direct-child arbitrary variant.

Live Demo
Plain CSSTailwind v4
Try it

Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time

Untitled UI React · Affiliate link

Get Untitled UI Pro

The code

<section class="tmd-11 w-full min-h-svh bg-tmd11-bg font-tmd11-sans text-tmd11-onsurface px-5 py-9" aria-label="Material Design expansion panels demo">
  <div class="w-[min(760px,100%)] mx-auto grid gap-6">
    <header class="grid gap-1.5">
      <h2 class="text-[26px] font-medium tracking-[-0.01em]">Expansion panels</h2>
      <p class="text-sm text-tmd11-onvariant">Native <code class="font-tmd11-mono text-[12px]">&lt;details name&gt;</code> exclusivity &mdash; open one and the browser closes the rest.</p>
    </header>

    <div class="grid gap-2">
      <details name="tmd11-acc" open class="group/p rounded-xl bg-tmd11-surface border border-tmd11-outlinevariant overflow-hidden transition-shadow duration-200 open:shadow-tmd11-1 motion-reduce:transition-none">
        <summary class="flex items-center gap-3 min-h-14 px-4 cursor-pointer list-none select-none text-[15px] font-medium [&amp;::-webkit-details-marker]:hidden focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd11-primary">
          <span class="font-tmd11-icon text-[20px] leading-none text-tmd11-primary" aria-hidden="true">tune</span>
          <span class="flex-1">Sensor configuration</span>
          <span class="font-tmd11-mono text-[11px] text-tmd11-onvariant">4 sensors</span>
          <span class="font-tmd11-icon text-[22px] leading-none text-tmd11-onvariant transition-transform duration-250 group-open/p:rotate-180 motion-reduce:transition-none" aria-hidden="true">expand_more</span>
        </summary>
        <div class="grid [grid-template-rows:0fr] transition-[grid-template-rows] duration-300 ease-out group-open/p:[grid-template-rows:1fr] motion-reduce:transition-none">
          <div class="min-h-0 overflow-hidden">
            <div class="grid gap-3 px-4 pt-1 pb-4 border-t border-tmd11-outlinevariant">
              <p class="text-sm leading-6 text-tmd11-onvariant text-pretty">The body animates from a <span class="font-tmd11-mono text-[12px]">0fr</span> grid track to <span class="font-tmd11-mono text-[12px]">1fr</span>. Padding lives on this inner element, never on the animating one &mdash; padding does not compress.</p>
              <div class="flex flex-wrap gap-2">
                <span class="inline-flex items-center h-8 px-3 rounded-lg bg-tmd11-primarycontainer text-tmd11-onprimarycontainer text-xs font-medium">CTD</span>
                <span class="inline-flex items-center h-8 px-3 rounded-lg bg-tmd11-primarycontainer text-tmd11-onprimarycontainer text-xs font-medium">Fluorometer</span>
                <span class="inline-flex items-center h-8 px-3 rounded-lg bg-tmd11-primarycontainer text-tmd11-onprimarycontainer text-xs font-medium">Oxygen optode</span>
                <span class="inline-flex items-center h-8 px-3 rounded-lg bg-tmd11-primarycontainer text-tmd11-onprimarycontainer text-xs font-medium">Turbidity</span>
              </div>
            </div>
          </div>
        </div>
      </details>

      <details name="tmd11-acc" class="group/p rounded-xl bg-tmd11-surface border border-tmd11-outlinevariant overflow-hidden transition-shadow duration-200 open:shadow-tmd11-1 motion-reduce:transition-none">
        <summary class="flex items-center gap-3 min-h-14 px-4 cursor-pointer list-none select-none text-[15px] font-medium [&amp;::-webkit-details-marker]:hidden focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd11-primary">
          <span class="font-tmd11-icon text-[20px] leading-none text-tmd11-primary" aria-hidden="true">schedule</span>
          <span class="flex-1">Sampling schedule</span>
          <span class="font-tmd11-mono text-[11px] text-tmd11-onvariant">hourly</span>
          <span class="font-tmd11-icon text-[22px] leading-none text-tmd11-onvariant transition-transform duration-250 group-open/p:rotate-180 motion-reduce:transition-none" aria-hidden="true">expand_more</span>
        </summary>
        <div class="grid [grid-template-rows:0fr] transition-[grid-template-rows] duration-300 ease-out group-open/p:[grid-template-rows:1fr] motion-reduce:transition-none">
          <div class="min-h-0 overflow-hidden">
            <div class="grid gap-3 px-4 pt-1 pb-4 border-t border-tmd11-outlinevariant">
              <p class="text-sm leading-6 text-tmd11-onvariant text-pretty">Opening this panel closed the one above &mdash; the browser did that, because both share <span class="font-tmd11-mono text-[12px]">name=&quot;tmd11-acc&quot;</span>.</p>
              <label class="flex items-center gap-3 min-h-11 text-sm cursor-pointer"><input type="checkbox" checked class="w-5 h-5 accent-tmd11-primary">Pause during transit</label>
            </div>
          </div>
        </div>
      </details>

      <details name="tmd11-acc" class="group/p rounded-xl bg-tmd11-surface border border-tmd11-outlinevariant overflow-hidden transition-shadow duration-200 open:shadow-tmd11-1 motion-reduce:transition-none">
        <summary class="flex items-center gap-3 min-h-14 px-4 cursor-pointer list-none select-none text-[15px] font-medium [&amp;::-webkit-details-marker]:hidden focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd11-primary">
          <span class="font-tmd11-icon text-[20px] leading-none text-tmd11-primary" aria-hidden="true">layers</span>
          <span class="flex-1">Nested panels</span>
          <span class="font-tmd11-icon text-[22px] leading-none text-tmd11-onvariant transition-transform duration-250 group-open/p:rotate-180 motion-reduce:transition-none" aria-hidden="true">expand_more</span>
        </summary>
        <div class="grid [grid-template-rows:0fr] transition-[grid-template-rows] duration-300 ease-out group-open/p:[grid-template-rows:1fr] motion-reduce:transition-none">
          <div class="min-h-0 overflow-hidden">
            <div class="grid gap-2 px-4 pt-3 pb-4 border-t border-tmd11-outlinevariant">
              <p class="text-sm leading-6 text-tmd11-onvariant text-pretty">This is why the group is <em>named</em>: an unnamed <span class="font-tmd11-mono text-[12px]">group-open:</span> would match through this open parent and flip the child's chevron too.</p>
              <details class="group/c rounded-lg bg-tmd11-surface2 overflow-hidden">
                <summary class="flex items-center gap-2 min-h-12 px-3 cursor-pointer list-none text-sm font-medium [&amp;::-webkit-details-marker]:hidden focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd11-primary"><span class="flex-1">Child panel &mdash; its own state</span><span class="font-tmd11-icon text-[20px] leading-none transition-transform duration-250 group-open/c:rotate-180" aria-hidden="true">expand_more</span></summary>
                <div class="grid [grid-template-rows:0fr] transition-[grid-template-rows] duration-250 group-open/c:[grid-template-rows:1fr]"><div class="min-h-0 overflow-hidden"><p class="px-3 pb-3 text-sm leading-6 text-tmd11-onvariant">Correct: the parent's chevron stays pointing down while this one turns.</p></div></div>
              </details>
            </div>
          </div>
        </div>
      </details>
    </div>

    <p class="text-sm leading-6 text-tmd11-onvariant text-pretty"><code class="font-tmd11-mono text-[12px]">&lt;details&gt;</code> also gives find-in-page reveal, a <code class="font-tmd11-mono text-[12px]">toggle</code> event and correct expanded-state announcement &mdash; none of which the checkbox-hack accordion had.</p>
  </div>
</section>
@import "tailwindcss";

@theme {
  --color-tmd11-bg: #f4f1f9;
  --color-tmd11-bg: oklch(0.963 0.011 303.1);
  --color-tmd11-surface: #fffbfe;
  --color-tmd11-surface: oklch(0.992 0.006 334);
  --color-tmd11-surface2: #f1eef8;
  --color-tmd11-surface2: oklch(0.954 0.014 299.8);
  --color-tmd11-primary: #5c6bc0;
  --color-tmd11-primary: oklch(0.556 0.132 273.8);
  --color-tmd11-primarycontainer: #e3e5f7;
  --color-tmd11-primarycontainer: oklch(0.926 0.024 280.8);
  --color-tmd11-onprimarycontainer: #131a45;
  --color-tmd11-onprimarycontainer: oklch(0.241 0.081 271.9);
  --color-tmd11-onsurface: #1c1b1f;
  --color-tmd11-onsurface: oklch(0.225 0.008 297.2);
  --color-tmd11-onvariant: #4c4a55;
  --color-tmd11-onvariant: oklch(0.415 0.018 293.9);
  --color-tmd11-outlinevariant: #d6d2e0;
  --color-tmd11-outlinevariant: oklch(0.871 0.02 299);
  --font-tmd11-sans: 'Roboto Flex', 'Roboto', system-ui, sans-serif;
  --font-tmd11-mono: 'Roboto Mono', ui-monospace, monospace;
  --font-tmd11-icon: 'Material Symbols Outlined';
  --shadow-tmd11-1: 0 1px 2px 0 rgb(0 0 0 / 0.3), 0 2px 6px 2px rgb(0 0 0 / 0.15);
}
/* The progressive upgrade: where supported, heights can interpolate to keyword values,
   which makes the 0fr/1fr grid dance unnecessary. Declared root-scoped, harmless
   everywhere else. There is no utility for it. */

.tmd-11 {
  width: 100%;
  min-height: 100svh;
  interpolate-size: allow-keywords;
}

.tmd-11 .font-tmd11-icon {
  font-feature-settings: 'liga';
}

Setup — do this once per project

Skip this if your project already runs Tailwind v4.

1Load Tailwind
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>

Compiles in the browser — perfect for a CodePen or a quick test, not for production.

2Add the stylesheetRequiredthe @theme block is what makes the custom utilities exist
@import "tailwindcss";

@theme {
  --color-tmd11-bg: #f4f1f9;
  --color-tmd11-bg: oklch(0.963 0.011 303.1);
  --color-tmd11-surface: #fffbfe;
  --color-tmd11-surface: oklch(0.992 0.006 334);
  --color-tmd11-surface2: #f1eef8;
  --color-tmd11-surface2: oklch(0.954 0.014 299.8);
  --color-tmd11-primary: #5c6bc0;
  --color-tmd11-primary: oklch(0.556 0.132 273.8);
  --color-tmd11-primarycontainer: #e3e5f7;
  --color-tmd11-primarycontainer: oklch(0.926 0.024 280.8);
  --color-tmd11-onprimarycontainer: #131a45;
  --color-tmd11-onprimarycontainer: oklch(0.241 0.081 271.9);
  --color-tmd11-onsurface: #1c1b1f;
  --color-tmd11-onsurface: oklch(0.225 0.008 297.2);
  --color-tmd11-onvariant: #4c4a55;
  --color-tmd11-onvariant: oklch(0.415 0.018 293.9);
  --color-tmd11-outlinevariant: #d6d2e0;
  --color-tmd11-outlinevariant: oklch(0.871 0.02 299);
  --font-tmd11-sans: 'Roboto Flex', 'Roboto', system-ui, sans-serif;
  --font-tmd11-mono: 'Roboto Mono', ui-monospace, monospace;
  --font-tmd11-icon: 'Material Symbols Outlined';
  --shadow-tmd11-1: 0 1px 2px 0 rgb(0 0 0 / 0.3), 0 2px 6px 2px rgb(0 0 0 / 0.15);
}
/* The progressive upgrade: where supported, heights can interpolate to keyword values,
   which makes the 0fr/1fr grid dance unnecessary. Declared root-scoped, harmless
   everywhere else. There is no utility for it. */

.tmd-11 {
  width: 100%;
  min-height: 100svh;
  interpolate-size: allow-keywords;
}

.tmd-11 .font-tmd11-icon {
  font-feature-settings: 'liga';
}
3Fonts used by this demooptional — skip if you have your own type stack
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto+Flex:opsz,wght@8..144,300..700&family=Roboto+Mono:wght@400;500&family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0..1,0&display=swap">

What changed in the port

Which parts became utilities, which stayed CSS, and why.

  • <details name> deletes the entire exclusive-accordion mechanism: no radios, no sibling selectors, no script, and the browser keeps the state consistent.
  • group-open/p: with a NAMED group is the correct form. A bare group-open: matches through every open ancestor and rotates nested chevrons — the single most common <details> porting bug.
  • The 0fr → 1fr track animation is unchanged from the CSS version in mechanism but now states both ends on the element, and interpolate-size is declared as the upgrade path.
  • The ::-webkit-details-marker shim and interpolate-size are the only stylesheet content — both are plumbing, neither is a design-scale value.
  • open:shadow-tmd11-1 puts M3's expanded elevation in the panel's own class list, replacing an .is-open rule.
  • Padding sits on an inner element, not the animating grid, which is what stops the closed-state sliver that plagues hand-rolled accordions.
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Material Design Expansion Panels and Accordion
Source: https://codefronts.com/design-styles/tailwind-material-design-components/tailwind-material-design-expansion-panels-accordion/

Expansion panels on <details>, with the one thing the checkbox-hack version could never have: name="…" makes the accordion exclusive natively, so opening one panel closes the others with no script and no radio hack. Tailwind styles the whole thing through open: and a direct-child arbitrary variant.
## HTML
```html
<section class="tmd-11 w-full min-h-svh bg-tmd11-bg font-tmd11-sans text-tmd11-onsurface px-5 py-9" aria-label="Material Design expansion panels demo">
  <div class="w-[min(760px,100%)] mx-auto grid gap-6">
    <header class="grid gap-1.5">
      <h2 class="text-[26px] font-medium tracking-[-0.01em]">Expansion panels</h2>
      <p class="text-sm text-tmd11-onvariant">Native <code class="font-tmd11-mono text-[12px]">&lt;details name&gt;</code> exclusivity &mdash; open one and the browser closes the rest.</p>
    </header>

    <div class="grid gap-2">
      <details name="tmd11-acc" open class="group/p rounded-xl bg-tmd11-surface border border-tmd11-outlinevariant overflow-hidden transition-shadow duration-200 open:shadow-tmd11-1 motion-reduce:transition-none">
        <summary class="flex items-center gap-3 min-h-14 px-4 cursor-pointer list-none select-none text-[15px] font-medium [&amp;::-webkit-details-marker]:hidden focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd11-primary">
          <span class="font-tmd11-icon text-[20px] leading-none text-tmd11-primary" aria-hidden="true">tune</span>
          <span class="flex-1">Sensor configuration</span>
          <span class="font-tmd11-mono text-[11px] text-tmd11-onvariant">4 sensors</span>
          <span class="font-tmd11-icon text-[22px] leading-none text-tmd11-onvariant transition-transform duration-250 group-open/p:rotate-180 motion-reduce:transition-none" aria-hidden="true">expand_more</span>
        </summary>
        <div class="grid [grid-template-rows:0fr] transition-[grid-template-rows] duration-300 ease-out group-open/p:[grid-template-rows:1fr] motion-reduce:transition-none">
          <div class="min-h-0 overflow-hidden">
            <div class="grid gap-3 px-4 pt-1 pb-4 border-t border-tmd11-outlinevariant">
              <p class="text-sm leading-6 text-tmd11-onvariant text-pretty">The body animates from a <span class="font-tmd11-mono text-[12px]">0fr</span> grid track to <span class="font-tmd11-mono text-[12px]">1fr</span>. Padding lives on this inner element, never on the animating one &mdash; padding does not compress.</p>
              <div class="flex flex-wrap gap-2">
                <span class="inline-flex items-center h-8 px-3 rounded-lg bg-tmd11-primarycontainer text-tmd11-onprimarycontainer text-xs font-medium">CTD</span>
                <span class="inline-flex items-center h-8 px-3 rounded-lg bg-tmd11-primarycontainer text-tmd11-onprimarycontainer text-xs font-medium">Fluorometer</span>
                <span class="inline-flex items-center h-8 px-3 rounded-lg bg-tmd11-primarycontainer text-tmd11-onprimarycontainer text-xs font-medium">Oxygen optode</span>
                <span class="inline-flex items-center h-8 px-3 rounded-lg bg-tmd11-primarycontainer text-tmd11-onprimarycontainer text-xs font-medium">Turbidity</span>
              </div>
            </div>
          </div>
        </div>
      </details>

      <details name="tmd11-acc" class="group/p rounded-xl bg-tmd11-surface border border-tmd11-outlinevariant overflow-hidden transition-shadow duration-200 open:shadow-tmd11-1 motion-reduce:transition-none">
        <summary class="flex items-center gap-3 min-h-14 px-4 cursor-pointer list-none select-none text-[15px] font-medium [&amp;::-webkit-details-marker]:hidden focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd11-primary">
          <span class="font-tmd11-icon text-[20px] leading-none text-tmd11-primary" aria-hidden="true">schedule</span>
          <span class="flex-1">Sampling schedule</span>
          <span class="font-tmd11-mono text-[11px] text-tmd11-onvariant">hourly</span>
          <span class="font-tmd11-icon text-[22px] leading-none text-tmd11-onvariant transition-transform duration-250 group-open/p:rotate-180 motion-reduce:transition-none" aria-hidden="true">expand_more</span>
        </summary>
        <div class="grid [grid-template-rows:0fr] transition-[grid-template-rows] duration-300 ease-out group-open/p:[grid-template-rows:1fr] motion-reduce:transition-none">
          <div class="min-h-0 overflow-hidden">
            <div class="grid gap-3 px-4 pt-1 pb-4 border-t border-tmd11-outlinevariant">
              <p class="text-sm leading-6 text-tmd11-onvariant text-pretty">Opening this panel closed the one above &mdash; the browser did that, because both share <span class="font-tmd11-mono text-[12px]">name=&quot;tmd11-acc&quot;</span>.</p>
              <label class="flex items-center gap-3 min-h-11 text-sm cursor-pointer"><input type="checkbox" checked class="w-5 h-5 accent-tmd11-primary">Pause during transit</label>
            </div>
          </div>
        </div>
      </details>

      <details name="tmd11-acc" class="group/p rounded-xl bg-tmd11-surface border border-tmd11-outlinevariant overflow-hidden transition-shadow duration-200 open:shadow-tmd11-1 motion-reduce:transition-none">
        <summary class="flex items-center gap-3 min-h-14 px-4 cursor-pointer list-none select-none text-[15px] font-medium [&amp;::-webkit-details-marker]:hidden focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd11-primary">
          <span class="font-tmd11-icon text-[20px] leading-none text-tmd11-primary" aria-hidden="true">layers</span>
          <span class="flex-1">Nested panels</span>
          <span class="font-tmd11-icon text-[22px] leading-none text-tmd11-onvariant transition-transform duration-250 group-open/p:rotate-180 motion-reduce:transition-none" aria-hidden="true">expand_more</span>
        </summary>
        <div class="grid [grid-template-rows:0fr] transition-[grid-template-rows] duration-300 ease-out group-open/p:[grid-template-rows:1fr] motion-reduce:transition-none">
          <div class="min-h-0 overflow-hidden">
            <div class="grid gap-2 px-4 pt-3 pb-4 border-t border-tmd11-outlinevariant">
              <p class="text-sm leading-6 text-tmd11-onvariant text-pretty">This is why the group is <em>named</em>: an unnamed <span class="font-tmd11-mono text-[12px]">group-open:</span> would match through this open parent and flip the child's chevron too.</p>
              <details class="group/c rounded-lg bg-tmd11-surface2 overflow-hidden">
                <summary class="flex items-center gap-2 min-h-12 px-3 cursor-pointer list-none text-sm font-medium [&amp;::-webkit-details-marker]:hidden focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd11-primary"><span class="flex-1">Child panel &mdash; its own state</span><span class="font-tmd11-icon text-[20px] leading-none transition-transform duration-250 group-open/c:rotate-180" aria-hidden="true">expand_more</span></summary>
                <div class="grid [grid-template-rows:0fr] transition-[grid-template-rows] duration-250 group-open/c:[grid-template-rows:1fr]"><div class="min-h-0 overflow-hidden"><p class="px-3 pb-3 text-sm leading-6 text-tmd11-onvariant">Correct: the parent's chevron stays pointing down while this one turns.</p></div></div>
              </details>
            </div>
          </div>
        </div>
      </details>
    </div>

    <p class="text-sm leading-6 text-tmd11-onvariant text-pretty"><code class="font-tmd11-mono text-[12px]">&lt;details&gt;</code> also gives find-in-page reveal, a <code class="font-tmd11-mono text-[12px]">toggle</code> event and correct expanded-state announcement &mdash; none of which the checkbox-hack accordion had.</p>
  </div>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-tmd11-bg: #f4f1f9;
  --color-tmd11-bg: oklch(0.963 0.011 303.1);
  --color-tmd11-surface: #fffbfe;
  --color-tmd11-surface: oklch(0.992 0.006 334);
  --color-tmd11-surface2: #f1eef8;
  --color-tmd11-surface2: oklch(0.954 0.014 299.8);
  --color-tmd11-primary: #5c6bc0;
  --color-tmd11-primary: oklch(0.556 0.132 273.8);
  --color-tmd11-primarycontainer: #e3e5f7;
  --color-tmd11-primarycontainer: oklch(0.926 0.024 280.8);
  --color-tmd11-onprimarycontainer: #131a45;
  --color-tmd11-onprimarycontainer: oklch(0.241 0.081 271.9);
  --color-tmd11-onsurface: #1c1b1f;
  --color-tmd11-onsurface: oklch(0.225 0.008 297.2);
  --color-tmd11-onvariant: #4c4a55;
  --color-tmd11-onvariant: oklch(0.415 0.018 293.9);
  --color-tmd11-outlinevariant: #d6d2e0;
  --color-tmd11-outlinevariant: oklch(0.871 0.02 299);
  --font-tmd11-sans: 'Roboto Flex', 'Roboto', system-ui, sans-serif;
  --font-tmd11-mono: 'Roboto Mono', ui-monospace, monospace;
  --font-tmd11-icon: 'Material Symbols Outlined';
  --shadow-tmd11-1: 0 1px 2px 0 rgb(0 0 0 / 0.3), 0 2px 6px 2px rgb(0 0 0 / 0.15);
}
/* The progressive upgrade: where supported, heights can interpolate to keyword values,
   which makes the 0fr/1fr grid dance unnecessary. Declared root-scoped, harmless
   everywhere else. There is no utility for it. */

.tmd-11 {
  width: 100%;
  min-height: 100svh;
  interpolate-size: allow-keywords;
}

.tmd-11 .font-tmd11-icon {
  font-feature-settings: 'liga';
}
```

How this works

Exclusivity is now a platform feature. Give every panel in a group the same name and the browser closes the others for you:

<details name="tmd11-acc" class="group/p rounded-xl open:shadow-tmd11-1">
  <summary class="list-none cursor-pointer …">…</summary>
  <div class="grid [grid-template-rows:0fr] transition-[grid-template-rows]
              group-open/p:[grid-template-rows:1fr]">
    <div class="min-h-0 overflow-hidden">…</div>

The CSS collection needed a radio group plus sibling selectors to fake this, and lost Esc, find-in-page reveal and the toggle event in the process.

Why the direct-child variant. group-open: matches through every open ancestor, so a nested panel inside an open parent inherits the open styling and rotates its own chevron the wrong way. Naming the group (group/p) and referencing it explicitly keeps each panel reading only its own state.

Animating to auto, two ways. The 0fr → 1fr grid track works everywhere Tailwind v4 does. interpolate-size: allow-keywords on the root is the newer, simpler answer where supported — both are declared, and the grid path is the one that actually runs.

Make it yours

  • Panel radius and gap: rounded-xl and the list's gap-2; set gap to 0 and add dividers for the M3 flush variant.
  • Remove name from the panels and they become independently expandable — one attribute, and the whole interaction model changes.
  • Chevron: any Material Symbols glyph works; the rotation is group-open/p:rotate-180.
  • Open the first panel by default with the open attribute — it is also what search engines index as visible content.
  • Swap the summary's min-h-14 for min-h-16 to match M3's two-line list item height.

Gotchas — read before shipping

  • group-open: on descendants of nested <details> is a real bug, not a style nit: use a named group or a direct-child arbitrary variant.
  • list-none alone does not remove the marker in Safari; the ::-webkit-details-marker shim is required.
  • The inner wrapper needs min-h-0 overflow-hidden or the 0fr track never compresses and every panel looks open.
  • Never put the panel's padding on the animating grid element — padding does not compress and leaves a visible sliver when closed.
  • <summary> must contain the accessible name; a decorative chevron needs aria-hidden.
  • Do not add role=&quot;button&quot; to <summary>; it already has the right role and the override breaks expanded-state announcement.

Browser support

ChromeSafariFirefoxEdge
129+not yetnot yet129+

Floor set by oklch(), interpolate-size as this demo is written. The core technique itself goes back further — Chrome 111+.

Exclusive accordions (<details name>) need Chrome 120, Safari 17.2 and Firefox 130. Where unsupported the panels simply all stay independently openable — a graceful loss. The 0fr row animation is Baseline 2023.

Techniques used in this demo

Theme tokensset in @theme, used by a utility

Search CodeFronts

Loading…