11 / 14
All 14 in 14 Material Design Components in Tailwind CSS
- 01Tailwind Material Design 3 Buttons
- 02Tailwind Material Design 3 Cards
- 03Tailwind Material Design Navigation Bar, Rail and Drawer
- 04Tailwind Material Design Text Fields and Form Controls
- 05Tailwind Material Design Dialog, Bottom Sheet and Snackbar
- 06Tailwind Material Design Data Table
- 07Tailwind Material Design Tabs with Sliding Indicator
- 08Tailwind Material Design Chips with Live Filtering
- 09Tailwind Material Design Stepper and Checkout Flow
- 10Tailwind Material Design Tooltips, Progress and Badges
- 11Tailwind Material Design Expansion Panels and Accordion
- 12Tailwind Material Design Lists and Swipeable Rows
- 13Tailwind Material Design Color Palette and Theme Switcher
- 14Tailwind Material Design Responsive App Shell Layout
Tailwind Material Design Expansion Panels and Accordion
Expansion panels on <details>, with the one thing the checkbox-hack version could never have: name="…" 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.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
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]"><details name></code> exclusivity — 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 [&::-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 — 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 [&::-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 — the browser did that, because both share <span class="font-tmd11-mono text-[12px]">name="tmd11-acc"</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 [&::-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 [&::-webkit-details-marker]:hidden focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd11-primary"><span class="flex-1">Child panel — 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]"><details></code> also gives find-in-page reveal, a <code class="font-tmd11-mono text-[12px]">toggle</code> event and correct expanded-state announcement — none of which the checkbox-hack accordion had.</p>
</div>
</section><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]"><details name></code> exclusivity — 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 [&::-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 — 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 [&::-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 — the browser did that, because both share <span class="font-tmd11-mono text-[12px]">name="tmd11-acc"</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 [&::-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 [&::-webkit-details-marker]:hidden focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd11-primary"><span class="flex-1">Child panel — 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]"><details></code> also gives find-in-page reveal, a <code class="font-tmd11-mono text-[12px]">toggle</code> event and correct expanded-state announcement — 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';
}@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.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<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.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@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';
}@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';
}<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">
<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 baregroup-open:matches through every open ancestor and rotates nested chevrons — the single most common<details>porting bug.- The
0fr → 1frtrack animation is unchanged from the CSS version in mechanism but now states both ends on the element, andinterpolate-sizeis declared as the upgrade path. - The
::-webkit-details-markershim andinterpolate-sizeare the only stylesheet content — both are plumbing, neither is a design-scale value. open:shadow-tmd11-1puts M3's expanded elevation in the panel's own class list, replacing an.is-openrule.- Padding sits on an inner element, not the animating grid, which is what stops the closed-state sliver that plagues hand-rolled accordions.
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]"><details name></code> exclusivity — 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 [&::-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 — 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 [&::-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 — the browser did that, because both share <span class="font-tmd11-mono text-[12px]">name="tmd11-acc"</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 [&::-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 [&::-webkit-details-marker]:hidden focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd11-primary"><span class="flex-1">Child panel — 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]"><details></code> also gives find-in-page reveal, a <code class="font-tmd11-mono text-[12px]">toggle</code> event and correct expanded-state announcement — 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';
}
```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]"><details name></code> exclusivity — 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 [&::-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 — 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 [&::-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 — the browser did that, because both share <span class="font-tmd11-mono text-[12px]">name="tmd11-acc"</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 [&::-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 [&::-webkit-details-marker]:hidden focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd11-primary"><span class="flex-1">Child panel — 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]"><details></code> also gives find-in-page reveal, a <code class="font-tmd11-mono text-[12px]">toggle</code> event and correct expanded-state announcement — 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-xland the list'sgap-2; set gap to 0 and add dividers for the M3 flush variant. - Remove
namefrom 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
openattribute — it is also what search engines index as visible content. - Swap the summary's
min-h-14formin-h-16to 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-nonealone does not remove the marker in Safari; the::-webkit-details-markershim is required.- The inner wrapper needs
min-h-0 overflow-hiddenor the0frtrack 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 needsaria-hidden.- Do not add
role="button"to<summary>; it already has the right role and the override breaks expanded-state announcement.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 129+ | not yet | not yet | 129+ |
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.