21 / 36
All 36 in 36 Tailwind Stacked Cards
- 01Tailwind Scroll-Driven Scaling Card Stack
- 02Tailwind Swipe Card Deck Drag to Dismiss
- 03Tailwind Glassmorphism Testimonial Card Stack
- 04Tailwind Stacked Cards Expand Accordion on Click
- 05Tailwind Fan Out Photo Stack on Hover
- 06Tailwind 3D Perspective Tilt Card Stack
- 07Tailwind Multi Step Form Card Stack
- 08Tailwind Tabbed Card Stack Bring to Front
- 09Tailwind Click to Front Stacked Cards Cycle
- 10Tailwind Classic Card Deck Stack Shadow
- 11Tailwind Fan Spread Hand of Cards
- 12Tailwind Polaroid Photo Stack Scattered Rotate
- 13Tailwind Staircase Offset Stacked Cards Cascade
- 14Tailwind Cards Spread Out Horizontally on Hover
- 15Tailwind Stacked Cards Lift on Hover Sibling Push
- 16Tailwind Accordion Stacked Cards Slide Out Menu
- 17Tailwind Shuffle Cards Animation Loop Deck
- 18Tailwind File Folder Tab Stacked Cards
- 19Tailwind Scattered Photos Pinch to Centre on Hover
- 20Tailwind Mobile Swipe Card Stack Translate3d
- 21Tailwind Single Accordion Card Nested Layers
- 22Tailwind Isometric Tower Stacked Cards 3D Column
- 23Tailwind 3D Flip Card Stack Reveal Back
- 24Tailwind Card Stack Tilt on Hover Quadrant
- 25Tailwind Notification Stack Pile Dismiss
- 26Tailwind Receipt Roll Unroll Stacked Items
- 27Tailwind Scratch Card Reveal Canvas Mask
- 28Tailwind Stacked Cards Hover Reveal Spread
- 29Tailwind Scroll Activated Sticky Stacking Cards
- 30Tailwind Stacked Testimonial Slider Radio
- 31Tailwind Isometric Overlapping Stacked Cards
- 32Tailwind Bento Grid Stacked Cards Hover Fan
- 33Tailwind Perspective Card Deck Depth Tunnel
- 34Tailwind Peel Back Corner Card Reveal
- 35Tailwind Timeline Stacked Milestone Cards
- 36Tailwind Spiral Tower Rotating Stacked Cards
Tailwind Single Accordion Card Nested Layers
Three panels inside one card whose height never changes. The open section takes the space by growing its flex, not by getting taller — has-checked:flex-1 — so nothing outside the card reflows and CLS stays at zero. The status dot is a before: utility rather than a pseudo-element rule, which is the small win: the dot's colour change is visible on the header it belongs to.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="stk-21 w-full min-h-svh flex items-center justify-center px-5 py-15 font-stk21-sans bg-stk21-bg" aria-label="Single accordion card">
<div class="flex w-[min(360px,100%)] h-80 flex-col overflow-hidden rounded-[20px] border border-stk21-line bg-stk21-card shadow-[0_26px_54px_-30px_rgba(0,0,0,0.8)]">
<div class="flex flex-none min-h-0 flex-col transition-[flex-grow] duration-450 ease-stk21 has-checked:flex-1 motion-reduce:transition-none">
<input class="peer sr-only" type="radio" name="stk-21" id="stk-21-1" checked>
<label class="flex items-center gap-2.5 border-t border-stk21-div px-5.5 py-4.5 text-base font-extrabold text-stk21-dim cursor-pointer first:border-t-0 before:size-2.25 before:shrink-0 before:rounded-full before:bg-stk21-off before:transition-colors before:duration-300 peer-checked:text-white peer-checked:before:bg-stk21-c1 peer-focus-visible:outline-3 peer-focus-visible:outline-stk21-c1 peer-focus-visible:-outline-offset-3" for="stk-21-1">Overview</label>
<div class="max-h-0 overflow-hidden opacity-0 transition-[max-height,opacity] duration-400 peer-checked:max-h-50 peer-checked:opacity-100 motion-reduce:transition-none">
<p class="m-0 px-5.5 pb-5.5 text-sm leading-relaxed text-stk21-body">A pocket knife of a component — three panels, one fixed card, zero layout shift.</p>
</div>
</div>
<div class="flex flex-none min-h-0 flex-col transition-[flex-grow] duration-450 ease-stk21 has-checked:flex-1 motion-reduce:transition-none">
<input class="peer sr-only" type="radio" name="stk-21" id="stk-21-2">
<label class="flex items-center gap-2.5 border-t border-stk21-div px-5.5 py-4.5 text-base font-extrabold text-stk21-dim cursor-pointer before:size-2.25 before:shrink-0 before:rounded-full before:bg-stk21-off before:transition-colors before:duration-300 peer-checked:text-white peer-checked:before:bg-stk21-c2 peer-focus-visible:outline-3 peer-focus-visible:outline-stk21-c2 peer-focus-visible:-outline-offset-3" for="stk-21-2">Features</label>
<div class="max-h-0 overflow-hidden opacity-0 transition-[max-height,opacity] duration-400 peer-checked:max-h-50 peer-checked:opacity-100 motion-reduce:transition-none">
<p class="m-0 px-5.5 pb-5.5 text-sm leading-relaxed text-stk21-body">Keyboard-navigable, reduced-motion aware, and copy-paste ready with namespaced tokens.</p>
</div>
</div>
<div class="flex flex-none min-h-0 flex-col transition-[flex-grow] duration-450 ease-stk21 has-checked:flex-1 motion-reduce:transition-none">
<input class="peer sr-only" type="radio" name="stk-21" id="stk-21-3">
<label class="flex items-center gap-2.5 border-t border-stk21-div px-5.5 py-4.5 text-base font-extrabold text-stk21-dim cursor-pointer before:size-2.25 before:shrink-0 before:rounded-full before:bg-stk21-off before:transition-colors before:duration-300 peer-checked:text-white peer-checked:before:bg-stk21-c3 peer-focus-visible:outline-3 peer-focus-visible:outline-stk21-c3 peer-focus-visible:-outline-offset-3" for="stk-21-3">Usage</label>
<div class="max-h-0 overflow-hidden opacity-0 transition-[max-height,opacity] duration-400 peer-checked:max-h-50 peer-checked:opacity-100 motion-reduce:transition-none">
<p class="m-0 px-5.5 pb-5.5 text-sm leading-relaxed text-stk21-body">Drop the markup in, keep the <code class="rounded-[5px] bg-stk21-code px-1.25 py-0.5 font-stk21-mono text-xs">stk-21</code> namespace, and swap the copy. Done.</p>
</div>
</div>
</div>
</section><section class="stk-21 w-full min-h-svh flex items-center justify-center px-5 py-15 font-stk21-sans bg-stk21-bg" aria-label="Single accordion card">
<div class="flex w-[min(360px,100%)] h-80 flex-col overflow-hidden rounded-[20px] border border-stk21-line bg-stk21-card shadow-[0_26px_54px_-30px_rgba(0,0,0,0.8)]">
<div class="flex flex-none min-h-0 flex-col transition-[flex-grow] duration-450 ease-stk21 has-checked:flex-1 motion-reduce:transition-none">
<input class="peer sr-only" type="radio" name="stk-21" id="stk-21-1" checked>
<label class="flex items-center gap-2.5 border-t border-stk21-div px-5.5 py-4.5 text-base font-extrabold text-stk21-dim cursor-pointer first:border-t-0 before:size-2.25 before:shrink-0 before:rounded-full before:bg-stk21-off before:transition-colors before:duration-300 peer-checked:text-white peer-checked:before:bg-stk21-c1 peer-focus-visible:outline-3 peer-focus-visible:outline-stk21-c1 peer-focus-visible:-outline-offset-3" for="stk-21-1">Overview</label>
<div class="max-h-0 overflow-hidden opacity-0 transition-[max-height,opacity] duration-400 peer-checked:max-h-50 peer-checked:opacity-100 motion-reduce:transition-none">
<p class="m-0 px-5.5 pb-5.5 text-sm leading-relaxed text-stk21-body">A pocket knife of a component — three panels, one fixed card, zero layout shift.</p>
</div>
</div>
<div class="flex flex-none min-h-0 flex-col transition-[flex-grow] duration-450 ease-stk21 has-checked:flex-1 motion-reduce:transition-none">
<input class="peer sr-only" type="radio" name="stk-21" id="stk-21-2">
<label class="flex items-center gap-2.5 border-t border-stk21-div px-5.5 py-4.5 text-base font-extrabold text-stk21-dim cursor-pointer before:size-2.25 before:shrink-0 before:rounded-full before:bg-stk21-off before:transition-colors before:duration-300 peer-checked:text-white peer-checked:before:bg-stk21-c2 peer-focus-visible:outline-3 peer-focus-visible:outline-stk21-c2 peer-focus-visible:-outline-offset-3" for="stk-21-2">Features</label>
<div class="max-h-0 overflow-hidden opacity-0 transition-[max-height,opacity] duration-400 peer-checked:max-h-50 peer-checked:opacity-100 motion-reduce:transition-none">
<p class="m-0 px-5.5 pb-5.5 text-sm leading-relaxed text-stk21-body">Keyboard-navigable, reduced-motion aware, and copy-paste ready with namespaced tokens.</p>
</div>
</div>
<div class="flex flex-none min-h-0 flex-col transition-[flex-grow] duration-450 ease-stk21 has-checked:flex-1 motion-reduce:transition-none">
<input class="peer sr-only" type="radio" name="stk-21" id="stk-21-3">
<label class="flex items-center gap-2.5 border-t border-stk21-div px-5.5 py-4.5 text-base font-extrabold text-stk21-dim cursor-pointer before:size-2.25 before:shrink-0 before:rounded-full before:bg-stk21-off before:transition-colors before:duration-300 peer-checked:text-white peer-checked:before:bg-stk21-c3 peer-focus-visible:outline-3 peer-focus-visible:outline-stk21-c3 peer-focus-visible:-outline-offset-3" for="stk-21-3">Usage</label>
<div class="max-h-0 overflow-hidden opacity-0 transition-[max-height,opacity] duration-400 peer-checked:max-h-50 peer-checked:opacity-100 motion-reduce:transition-none">
<p class="m-0 px-5.5 pb-5.5 text-sm leading-relaxed text-stk21-body">Drop the markup in, keep the <code class="rounded-[5px] bg-stk21-code px-1.25 py-0.5 font-stk21-mono text-xs">stk-21</code> namespace, and swap the copy. Done.</p>
</div>
</div>
</div>
</section>@import "tailwindcss";
@theme {
--color-stk21-bg: #101219;
--color-stk21-bg: oklch(0.19 0.012 265);
--color-stk21-card: #1b1e28;
--color-stk21-card: oklch(0.24 0.014 265);
--color-stk21-line: #2a2e3a;
--color-stk21-line: oklch(0.32 0.016 265);
--color-stk21-div: #262a36;
--color-stk21-div: oklch(0.30 0.014 265);
--color-stk21-code: #0f1117;
--color-stk21-code: oklch(0.17 0.012 265);
--color-stk21-off: #3a4050;
--color-stk21-off: oklch(0.40 0.018 265);
--color-stk21-dim: #c9cde0;
--color-stk21-dim: oklch(0.85 0.018 265);
--color-stk21-body: #aab0c2;
--color-stk21-body: oklch(0.75 0.018 265);
--color-stk21-c1: #e07a5f;
--color-stk21-c1: oklch(0.66 0.16 20);
--color-stk21-c2: #4bbd8c;
--color-stk21-c2: oklch(0.68 0.15 150);
--color-stk21-c3: #6a83e8;
--color-stk21-c3: oklch(0.66 0.16 265);
--font-stk21-sans: 'Archivo', system-ui, sans-serif;
--font-stk21-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-stk21: cubic-bezier(0.3, 0.9, 0.3, 1);
}@import "tailwindcss";
@theme {
--color-stk21-bg: #101219;
--color-stk21-bg: oklch(0.19 0.012 265);
--color-stk21-card: #1b1e28;
--color-stk21-card: oklch(0.24 0.014 265);
--color-stk21-line: #2a2e3a;
--color-stk21-line: oklch(0.32 0.016 265);
--color-stk21-div: #262a36;
--color-stk21-div: oklch(0.30 0.014 265);
--color-stk21-code: #0f1117;
--color-stk21-code: oklch(0.17 0.012 265);
--color-stk21-off: #3a4050;
--color-stk21-off: oklch(0.40 0.018 265);
--color-stk21-dim: #c9cde0;
--color-stk21-dim: oklch(0.85 0.018 265);
--color-stk21-body: #aab0c2;
--color-stk21-body: oklch(0.75 0.018 265);
--color-stk21-c1: #e07a5f;
--color-stk21-c1: oklch(0.66 0.16 20);
--color-stk21-c2: #4bbd8c;
--color-stk21-c2: oklch(0.68 0.15 150);
--color-stk21-c3: #6a83e8;
--color-stk21-c3: oklch(0.66 0.16 265);
--font-stk21-sans: 'Archivo', system-ui, sans-serif;
--font-stk21-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-stk21: cubic-bezier(0.3, 0.9, 0.3, 1);
}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-stk21-bg: #101219;
--color-stk21-bg: oklch(0.19 0.012 265);
--color-stk21-card: #1b1e28;
--color-stk21-card: oklch(0.24 0.014 265);
--color-stk21-line: #2a2e3a;
--color-stk21-line: oklch(0.32 0.016 265);
--color-stk21-div: #262a36;
--color-stk21-div: oklch(0.30 0.014 265);
--color-stk21-code: #0f1117;
--color-stk21-code: oklch(0.17 0.012 265);
--color-stk21-off: #3a4050;
--color-stk21-off: oklch(0.40 0.018 265);
--color-stk21-dim: #c9cde0;
--color-stk21-dim: oklch(0.85 0.018 265);
--color-stk21-body: #aab0c2;
--color-stk21-body: oklch(0.75 0.018 265);
--color-stk21-c1: #e07a5f;
--color-stk21-c1: oklch(0.66 0.16 20);
--color-stk21-c2: #4bbd8c;
--color-stk21-c2: oklch(0.68 0.15 150);
--color-stk21-c3: #6a83e8;
--color-stk21-c3: oklch(0.66 0.16 265);
--font-stk21-sans: 'Archivo', system-ui, sans-serif;
--font-stk21-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-stk21: cubic-bezier(0.3, 0.9, 0.3, 1);
}@import "tailwindcss";
@theme {
--color-stk21-bg: #101219;
--color-stk21-bg: oklch(0.19 0.012 265);
--color-stk21-card: #1b1e28;
--color-stk21-card: oklch(0.24 0.014 265);
--color-stk21-line: #2a2e3a;
--color-stk21-line: oklch(0.32 0.016 265);
--color-stk21-div: #262a36;
--color-stk21-div: oklch(0.30 0.014 265);
--color-stk21-code: #0f1117;
--color-stk21-code: oklch(0.17 0.012 265);
--color-stk21-off: #3a4050;
--color-stk21-off: oklch(0.40 0.018 265);
--color-stk21-dim: #c9cde0;
--color-stk21-dim: oklch(0.85 0.018 265);
--color-stk21-body: #aab0c2;
--color-stk21-body: oklch(0.75 0.018 265);
--color-stk21-c1: #e07a5f;
--color-stk21-c1: oklch(0.66 0.16 20);
--color-stk21-c2: #4bbd8c;
--color-stk21-c2: oklch(0.68 0.15 150);
--color-stk21-c3: #6a83e8;
--color-stk21-c3: oklch(0.66 0.16 265);
--font-stk21-sans: 'Archivo', system-ui, sans-serif;
--font-stk21-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-stk21: cubic-bezier(0.3, 0.9, 0.3, 1);
}<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=Archivo:wdth,wght@75..125,400;75..125,800&family=JetBrains+Mono:wght@400;500&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=Archivo:wdth,wght@75..125,400;75..125,800&family=JetBrains+Mono:wght@400;500&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- The webfonts load as
<link>tags insetup, not as an@importinside the stylesheet. Tailwind emits user@importrules after its own@layerblocks, and a late@importis invalid CSS — the browser drops it and the demo silently falls back to system fonts. has-checked:flex-1is the whole mechanism: the open panel takes space from its siblings inside a fixed box, so the page outside never reflows and CLS stays at zero.- The status dot is built entirely in utilities —
before:size-2.25 before:rounded-full before:bg-stk21-offpluspeer-checked:before:bg-stk21-c1— instead of a::beforerule and a second rule to recolour it. first:border-t-0replaces the:first-childdivider rule, on the element it applies to.peer-checked:for the header and body,has-checked:for the section: the same sibling-versus-descendant split as demo 04, and the same trap.min-h-0on each section is load-bearing — without it flex items will not shrink below their content and the fixed card overflows.- The focus ring uses a negative offset (
-outline-offset-3) because the card clips its children and a positive offset would be cut off.
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 Single Accordion Card Nested Layers
Source: https://codefronts.com/components/tailwind-stacked-cards/tailwind-single-accordion-card-nested-layers/
Three panels inside one card whose height never changes. The open section takes the space by growing its flex, not by getting taller — has-checked:flex-1 — so nothing outside the card reflows and CLS stays at zero. The status dot is a before: utility rather than a pseudo-element rule, which is the small win: the dot's colour change is visible on the header it belongs to.
## HTML
```html
<section class="stk-21 w-full min-h-svh flex items-center justify-center px-5 py-15 font-stk21-sans bg-stk21-bg" aria-label="Single accordion card">
<div class="flex w-[min(360px,100%)] h-80 flex-col overflow-hidden rounded-[20px] border border-stk21-line bg-stk21-card shadow-[0_26px_54px_-30px_rgba(0,0,0,0.8)]">
<div class="flex flex-none min-h-0 flex-col transition-[flex-grow] duration-450 ease-stk21 has-checked:flex-1 motion-reduce:transition-none">
<input class="peer sr-only" type="radio" name="stk-21" id="stk-21-1" checked>
<label class="flex items-center gap-2.5 border-t border-stk21-div px-5.5 py-4.5 text-base font-extrabold text-stk21-dim cursor-pointer first:border-t-0 before:size-2.25 before:shrink-0 before:rounded-full before:bg-stk21-off before:transition-colors before:duration-300 peer-checked:text-white peer-checked:before:bg-stk21-c1 peer-focus-visible:outline-3 peer-focus-visible:outline-stk21-c1 peer-focus-visible:-outline-offset-3" for="stk-21-1">Overview</label>
<div class="max-h-0 overflow-hidden opacity-0 transition-[max-height,opacity] duration-400 peer-checked:max-h-50 peer-checked:opacity-100 motion-reduce:transition-none">
<p class="m-0 px-5.5 pb-5.5 text-sm leading-relaxed text-stk21-body">A pocket knife of a component — three panels, one fixed card, zero layout shift.</p>
</div>
</div>
<div class="flex flex-none min-h-0 flex-col transition-[flex-grow] duration-450 ease-stk21 has-checked:flex-1 motion-reduce:transition-none">
<input class="peer sr-only" type="radio" name="stk-21" id="stk-21-2">
<label class="flex items-center gap-2.5 border-t border-stk21-div px-5.5 py-4.5 text-base font-extrabold text-stk21-dim cursor-pointer before:size-2.25 before:shrink-0 before:rounded-full before:bg-stk21-off before:transition-colors before:duration-300 peer-checked:text-white peer-checked:before:bg-stk21-c2 peer-focus-visible:outline-3 peer-focus-visible:outline-stk21-c2 peer-focus-visible:-outline-offset-3" for="stk-21-2">Features</label>
<div class="max-h-0 overflow-hidden opacity-0 transition-[max-height,opacity] duration-400 peer-checked:max-h-50 peer-checked:opacity-100 motion-reduce:transition-none">
<p class="m-0 px-5.5 pb-5.5 text-sm leading-relaxed text-stk21-body">Keyboard-navigable, reduced-motion aware, and copy-paste ready with namespaced tokens.</p>
</div>
</div>
<div class="flex flex-none min-h-0 flex-col transition-[flex-grow] duration-450 ease-stk21 has-checked:flex-1 motion-reduce:transition-none">
<input class="peer sr-only" type="radio" name="stk-21" id="stk-21-3">
<label class="flex items-center gap-2.5 border-t border-stk21-div px-5.5 py-4.5 text-base font-extrabold text-stk21-dim cursor-pointer before:size-2.25 before:shrink-0 before:rounded-full before:bg-stk21-off before:transition-colors before:duration-300 peer-checked:text-white peer-checked:before:bg-stk21-c3 peer-focus-visible:outline-3 peer-focus-visible:outline-stk21-c3 peer-focus-visible:-outline-offset-3" for="stk-21-3">Usage</label>
<div class="max-h-0 overflow-hidden opacity-0 transition-[max-height,opacity] duration-400 peer-checked:max-h-50 peer-checked:opacity-100 motion-reduce:transition-none">
<p class="m-0 px-5.5 pb-5.5 text-sm leading-relaxed text-stk21-body">Drop the markup in, keep the <code class="rounded-[5px] bg-stk21-code px-1.25 py-0.5 font-stk21-mono text-xs">stk-21</code> namespace, and swap the copy. Done.</p>
</div>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-stk21-bg: #101219;
--color-stk21-bg: oklch(0.19 0.012 265);
--color-stk21-card: #1b1e28;
--color-stk21-card: oklch(0.24 0.014 265);
--color-stk21-line: #2a2e3a;
--color-stk21-line: oklch(0.32 0.016 265);
--color-stk21-div: #262a36;
--color-stk21-div: oklch(0.30 0.014 265);
--color-stk21-code: #0f1117;
--color-stk21-code: oklch(0.17 0.012 265);
--color-stk21-off: #3a4050;
--color-stk21-off: oklch(0.40 0.018 265);
--color-stk21-dim: #c9cde0;
--color-stk21-dim: oklch(0.85 0.018 265);
--color-stk21-body: #aab0c2;
--color-stk21-body: oklch(0.75 0.018 265);
--color-stk21-c1: #e07a5f;
--color-stk21-c1: oklch(0.66 0.16 20);
--color-stk21-c2: #4bbd8c;
--color-stk21-c2: oklch(0.68 0.15 150);
--color-stk21-c3: #6a83e8;
--color-stk21-c3: oklch(0.66 0.16 265);
--font-stk21-sans: 'Archivo', system-ui, sans-serif;
--font-stk21-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-stk21: cubic-bezier(0.3, 0.9, 0.3, 1);
}
```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 Single Accordion Card Nested Layers
Source: https://codefronts.com/components/tailwind-stacked-cards/tailwind-single-accordion-card-nested-layers/
Three panels inside one card whose height never changes. The open section takes the space by growing its flex, not by getting taller — has-checked:flex-1 — so nothing outside the card reflows and CLS stays at zero. The status dot is a before: utility rather than a pseudo-element rule, which is the small win: the dot's colour change is visible on the header it belongs to.
## HTML
```html
<section class="stk-21 w-full min-h-svh flex items-center justify-center px-5 py-15 font-stk21-sans bg-stk21-bg" aria-label="Single accordion card">
<div class="flex w-[min(360px,100%)] h-80 flex-col overflow-hidden rounded-[20px] border border-stk21-line bg-stk21-card shadow-[0_26px_54px_-30px_rgba(0,0,0,0.8)]">
<div class="flex flex-none min-h-0 flex-col transition-[flex-grow] duration-450 ease-stk21 has-checked:flex-1 motion-reduce:transition-none">
<input class="peer sr-only" type="radio" name="stk-21" id="stk-21-1" checked>
<label class="flex items-center gap-2.5 border-t border-stk21-div px-5.5 py-4.5 text-base font-extrabold text-stk21-dim cursor-pointer first:border-t-0 before:size-2.25 before:shrink-0 before:rounded-full before:bg-stk21-off before:transition-colors before:duration-300 peer-checked:text-white peer-checked:before:bg-stk21-c1 peer-focus-visible:outline-3 peer-focus-visible:outline-stk21-c1 peer-focus-visible:-outline-offset-3" for="stk-21-1">Overview</label>
<div class="max-h-0 overflow-hidden opacity-0 transition-[max-height,opacity] duration-400 peer-checked:max-h-50 peer-checked:opacity-100 motion-reduce:transition-none">
<p class="m-0 px-5.5 pb-5.5 text-sm leading-relaxed text-stk21-body">A pocket knife of a component — three panels, one fixed card, zero layout shift.</p>
</div>
</div>
<div class="flex flex-none min-h-0 flex-col transition-[flex-grow] duration-450 ease-stk21 has-checked:flex-1 motion-reduce:transition-none">
<input class="peer sr-only" type="radio" name="stk-21" id="stk-21-2">
<label class="flex items-center gap-2.5 border-t border-stk21-div px-5.5 py-4.5 text-base font-extrabold text-stk21-dim cursor-pointer before:size-2.25 before:shrink-0 before:rounded-full before:bg-stk21-off before:transition-colors before:duration-300 peer-checked:text-white peer-checked:before:bg-stk21-c2 peer-focus-visible:outline-3 peer-focus-visible:outline-stk21-c2 peer-focus-visible:-outline-offset-3" for="stk-21-2">Features</label>
<div class="max-h-0 overflow-hidden opacity-0 transition-[max-height,opacity] duration-400 peer-checked:max-h-50 peer-checked:opacity-100 motion-reduce:transition-none">
<p class="m-0 px-5.5 pb-5.5 text-sm leading-relaxed text-stk21-body">Keyboard-navigable, reduced-motion aware, and copy-paste ready with namespaced tokens.</p>
</div>
</div>
<div class="flex flex-none min-h-0 flex-col transition-[flex-grow] duration-450 ease-stk21 has-checked:flex-1 motion-reduce:transition-none">
<input class="peer sr-only" type="radio" name="stk-21" id="stk-21-3">
<label class="flex items-center gap-2.5 border-t border-stk21-div px-5.5 py-4.5 text-base font-extrabold text-stk21-dim cursor-pointer before:size-2.25 before:shrink-0 before:rounded-full before:bg-stk21-off before:transition-colors before:duration-300 peer-checked:text-white peer-checked:before:bg-stk21-c3 peer-focus-visible:outline-3 peer-focus-visible:outline-stk21-c3 peer-focus-visible:-outline-offset-3" for="stk-21-3">Usage</label>
<div class="max-h-0 overflow-hidden opacity-0 transition-[max-height,opacity] duration-400 peer-checked:max-h-50 peer-checked:opacity-100 motion-reduce:transition-none">
<p class="m-0 px-5.5 pb-5.5 text-sm leading-relaxed text-stk21-body">Drop the markup in, keep the <code class="rounded-[5px] bg-stk21-code px-1.25 py-0.5 font-stk21-mono text-xs">stk-21</code> namespace, and swap the copy. Done.</p>
</div>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-stk21-bg: #101219;
--color-stk21-bg: oklch(0.19 0.012 265);
--color-stk21-card: #1b1e28;
--color-stk21-card: oklch(0.24 0.014 265);
--color-stk21-line: #2a2e3a;
--color-stk21-line: oklch(0.32 0.016 265);
--color-stk21-div: #262a36;
--color-stk21-div: oklch(0.30 0.014 265);
--color-stk21-code: #0f1117;
--color-stk21-code: oklch(0.17 0.012 265);
--color-stk21-off: #3a4050;
--color-stk21-off: oklch(0.40 0.018 265);
--color-stk21-dim: #c9cde0;
--color-stk21-dim: oklch(0.85 0.018 265);
--color-stk21-body: #aab0c2;
--color-stk21-body: oklch(0.75 0.018 265);
--color-stk21-c1: #e07a5f;
--color-stk21-c1: oklch(0.66 0.16 20);
--color-stk21-c2: #4bbd8c;
--color-stk21-c2: oklch(0.68 0.15 150);
--color-stk21-c3: #6a83e8;
--color-stk21-c3: oklch(0.66 0.16 265);
--font-stk21-sans: 'Archivo', system-ui, sans-serif;
--font-stk21-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-stk21: cubic-bezier(0.3, 0.9, 0.3, 1);
}
```How this works
Grow, do not expand. The card is a fixed-height flex column. Panels rest at flex-none (just their headers) and the open one becomes flex-1:
<div class="flex flex-col flex-none min-h-0
transition-[flex-grow] duration-450
has-checked:flex-1">Because the growth is redistribution inside a fixed box, the surrounding layout never learns anything happened — the difference between an accordion that costs you CLS and one that does not.
Pseudo-elements are utilities here. before: creates the status dot, sizes it, rounds it and colours it, and peer-checked:before:bg-stk21-c1 switches it on. The CSS version needs a ::before rule plus a second rule to recolour it; the utility version keeps both on the header, so it is obvious what the dot is for.
peer for the header, has for the section. Same division as demo 04: the radio is a sibling of the header and the body, but a descendant of the section. Two families of variant, one input.
Make it yours
- Switch to checkboxes for several sections open at once;
has-checked:flex-1then shares the space between them. - Change the collapsed header height with the header's
py-4.5. - Retime with
duration-450on the section andduration-400on the body. - Recolour each section's dot through its own
peer-checked:before:bg-stk21-*. - Change the card's fixed height (
h-80); the panels redistribute automatically.
Gotchas — read before shipping
- Animate
flex-grow, not height. Height animation on the card is exactly the layout shift this pattern exists to avoid. min-h-0on the sections is required, or flex items refuse to shrink below their content and the card overflows.- The body needs
overflow-hiddenplus amax-h-*pair;opacityalone leaves collapsed text painting over its neighbour. - Use
has-checked:on the section andpeer-checked:on the header — the input is a descendant of one and a sibling of the other. - Keep the radios
sr-onlyso arrow-key navigation between panels still works. - Give the focus ring a negative offset; on a header inside a clipped card, a positive offset is cut off.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Flexbox flex-grow transitions and radio :checked are universal (Chrome 49+, Safari 10+, Firefox 52+); has-checked: compiles to :has(), inside Tailwind v4's own floor.