25 / 36
All 36 in 36 Tailwind CSS Modals
- 01Tailwind Login / Signup Modal with Native Dialog & Starting-Style
- 02Tailwind Password Reset Modal with In-Place Success State
- 03Tailwind Age Verification Gate with :has() and Zero JavaScript
- 04Tailwind Paywall Modal with Masked Article Fade
- 05Tailwind Product Quick-View Modal with has-checked Variant Chips
- 06Tailwind Shopping Cart Slide-Over Drawer with Dialog
- 07Tailwind Timed Newsletter Promo Popup with Frequency Cap
- 08Tailwind Size Guide Overlay with Zero-JS cm/inch Toggle
- 09Tailwind Zero-JS Lightbox Gallery with the target Variant
- 10Tailwind Video Trailer Modal with Aspect-Locked Iframe
- 11Tailwind Destructive Delete Modal with Type-to-Confirm Gate
- 12Tailwind Success / Error Result Modal with Drawn SVG Icon
- 13Tailwind GDPR Cookie Consent Banner with details Disclosure
- 14Tailwind Multi-Step Onboarding Modal with data-step Variants
- 15Tailwind Filter & Sort Drawer that Becomes a Bottom Sheet
- 16Tailwind Contact Support Modal with Live Validation
- 17Tailwind Tabbed Settings Modal with Zero-JS Radio Tabs
- 18Tailwind Command Palette Modal with ⌘K and Roving Selection
- 19Tailwind Confirm Delete Modal with Corner Slide-and-Spin Entrance
- 20Tailwind Login Modal with Staggered Blur-In Content Drop
- 21Tailwind Signup Modal with 3D Origami Fan Deploy Panels
- 22Tailwind Payment Checkout Modal with 3D Paper Fold Entrance
- 23Tailwind HIPAA Patient Consent Modal with Scroll-Gated Agreement
- 24Tailwind Age Verification Modal with Clip-Path Iris Aperture
- 25Tailwind GDPR Cookie Preferences Modal with Unfolding Category Rows
- 26Tailwind Video Lightbox Modal with Chromatic Glitch Materialize
- 27Tailwind Image Gallery Lightbox with Clip-Path Diamond Reveal
- 28Tailwind Add to Cart Confirmation with SVG Liquid Morph Entrance
- 29Tailwind Product Quick View Modal with Elastic Button Morph
- 30Tailwind Newsletter Signup Modal with Depth-Layer Parallax Entrance
- 31Tailwind Exit-Intent Discount Modal with Live Countdown and Coupon Copy
- 32Tailwind Restaurant Reservation Modal with Cascading Fan Panels
- 33Tailwind Order Success Modal with CSS-Only Confetti Burst
- 34Tailwind SaaS Onboarding Welcome Modal with Cascading Blur-In
- 35Tailwind Terms of Service Modal with Letter-Slide and Consent Gate
- 36Tailwind Password Reset Modal with Enumeration-Safe Confirmation
Tailwind GDPR Cookie Preferences Modal with Unfolding Category Rows
A consent centre whose four category rows unfold downward like origami while the title and the three actions are readable from frame one — the fast path stays fast. The port drives the cascade from the dialog's own [open] state with in-[dialog[open]]: plus per-row [transition-delay:…], and builds the toggles from peer-checked: instead of custom controls.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tw-07 w-full min-h-svh grid place-items-center bg-tw07-bg font-tw07-sans px-6 py-10">
<button type="button" data-tw07-open class="inline-flex items-center justify-center min-h-11 px-6 rounded-lg border border-tw07-line bg-tw07-card text-[15px] font-semibold text-tw07-ink cursor-pointer transition-colors duration-200 hover:bg-tw07-well2 focus-visible:outline-2 focus-visible:outline-tw07-ink focus-visible:outline-offset-2">Cookie preferences</button>
<dialog id="tw-07-dlg" aria-labelledby="tw-07-title" class="m-auto w-[min(560px,94vw)] p-0 border-0 rounded-[14px] text-left text-tw07-ink bg-tw07-card shadow-[0_40px_60px_-20px_#0f172a59] opacity-0 transition-[opacity,overlay,display] duration-200 transition-discrete open:opacity-100 open:starting:opacity-0 open:backdrop:bg-tw07-scrim open:backdrop:backdrop-blur-[4px]">
<div class="relative px-8 pt-8 pb-6">
<button type="button" data-tw07-close aria-label="Close dialog" class="absolute top-3.5 right-3.5 grid place-items-center size-9 rounded-lg border-0 bg-transparent text-tw07-sub text-base cursor-pointer transition-colors duration-200 hover:bg-tw07-well2 hover:text-tw07-ink focus-visible:outline-2 focus-visible:outline-tw07-ink focus-visible:outline-offset-2">✕</button>
<h2 id="tw-07-title" class="mb-2 text-[1.35rem] font-extrabold tracking-[-0.015em] leading-tight">Cookie preferences</h2>
<p class="mb-5 text-sm leading-relaxed text-tw07-sub text-pretty">We use cookies to enhance your experience, analyze site traffic, and improve services. You can accept all, reject all non-essential, or customize per category.</p>
<div class="flex flex-col gap-2 mb-5 perspective-[900px]">
<div class="px-4 py-3.5 rounded-[9px] border border-tw07-line bg-tw07-well origin-bottom backface-hidden opacity-0 rotate-x-[-180deg] transition-[opacity,rotate] duration-700 ease-[cubic-bezier(.4,.15,.25,1)] in-[dialog[open]]:opacity-100 in-[dialog[open]]:rotate-x-0 [transition-delay:0ms] motion-reduce:transition-opacity motion-reduce:rotate-x-0 motion-reduce:[transition-delay:0ms]">
<div class="flex justify-between items-start gap-3.5">
<div>
<h3 class="mb-0.5 text-sm font-bold">Strictly necessary</h3>
<p class="text-xs leading-snug text-tw07-sub">Session cookies, CSRF tokens, load balancing. Cannot be disabled.</p>
</div>
<span class="shrink-0 grid place-items-center w-[42px] h-[22px] rounded-full bg-tw07-locked text-[0.62rem] font-extrabold tracking-wider text-white">ON</span>
</div>
</div>
<div class="px-4 py-3.5 rounded-[9px] border border-tw07-line bg-tw07-well origin-bottom backface-hidden opacity-0 rotate-x-[-180deg] transition-[opacity,rotate] duration-700 ease-[cubic-bezier(.4,.15,.25,1)] in-[dialog[open]]:opacity-100 in-[dialog[open]]:rotate-x-0 [transition-delay:180ms] motion-reduce:transition-opacity motion-reduce:rotate-x-0 motion-reduce:[transition-delay:0ms]">
<div class="flex justify-between items-start gap-3.5">
<div>
<h3 class="mb-0.5 text-sm font-bold">Analytics</h3>
<p class="text-xs leading-snug text-tw07-sub">Google Analytics, PostHog. Helps us understand aggregate usage patterns.</p>
</div>
<label class="shrink-0 cursor-pointer">
<input type="checkbox" checked class="peer sr-only">
<span class="sr-only">Allow analytics cookies</span>
<span aria-hidden="true" class="grid items-center w-[42px] h-[22px] rounded-full bg-tw07-off transition-colors duration-200 peer-checked:bg-tw07-good peer-focus-visible:outline-2 peer-focus-visible:outline-tw07-ink peer-focus-visible:outline-offset-2"><span class="block size-[18px] ml-0.5 rounded-full bg-white transition-transform duration-200 peer-checked:translate-x-5 motion-reduce:transition-none"></span></span>
</label>
</div>
</div>
<div class="px-4 py-3.5 rounded-[9px] border border-tw07-line bg-tw07-well origin-bottom backface-hidden opacity-0 rotate-x-[-180deg] transition-[opacity,rotate] duration-700 ease-[cubic-bezier(.4,.15,.25,1)] in-[dialog[open]]:opacity-100 in-[dialog[open]]:rotate-x-0 [transition-delay:360ms] motion-reduce:transition-opacity motion-reduce:rotate-x-0 motion-reduce:[transition-delay:0ms]">
<div class="flex justify-between items-start gap-3.5">
<div>
<h3 class="mb-0.5 text-sm font-bold">Marketing</h3>
<p class="text-xs leading-snug text-tw07-sub">Facebook Pixel, LinkedIn Insight Tag. Enables retargeting and personalized ads.</p>
</div>
<label class="shrink-0 cursor-pointer">
<input type="checkbox" class="peer sr-only">
<span class="sr-only">Allow marketing cookies</span>
<span aria-hidden="true" class="grid items-center w-[42px] h-[22px] rounded-full bg-tw07-off transition-colors duration-200 peer-checked:bg-tw07-good peer-focus-visible:outline-2 peer-focus-visible:outline-tw07-ink peer-focus-visible:outline-offset-2"><span class="block size-[18px] ml-0.5 rounded-full bg-white transition-transform duration-200 peer-checked:translate-x-5 motion-reduce:transition-none"></span></span>
</label>
</div>
</div>
<div class="px-4 py-3.5 rounded-[9px] border border-tw07-line bg-tw07-well origin-bottom backface-hidden opacity-0 rotate-x-[-180deg] transition-[opacity,rotate] duration-700 ease-[cubic-bezier(.4,.15,.25,1)] in-[dialog[open]]:opacity-100 in-[dialog[open]]:rotate-x-0 [transition-delay:540ms] motion-reduce:transition-opacity motion-reduce:rotate-x-0 motion-reduce:[transition-delay:0ms]">
<div class="flex justify-between items-start gap-3.5">
<div>
<h3 class="mb-0.5 text-sm font-bold">Personalization</h3>
<p class="text-xs leading-snug text-tw07-sub">Recommendation engine, saved preferences, theme mode. Improves your experience.</p>
</div>
<label class="shrink-0 cursor-pointer">
<input type="checkbox" checked class="peer sr-only">
<span class="sr-only">Allow personalization cookies</span>
<span aria-hidden="true" class="grid items-center w-[42px] h-[22px] rounded-full bg-tw07-off transition-colors duration-200 peer-checked:bg-tw07-good peer-focus-visible:outline-2 peer-focus-visible:outline-tw07-ink peer-focus-visible:outline-offset-2"><span class="block size-[18px] ml-0.5 rounded-full bg-white transition-transform duration-200 peer-checked:translate-x-5 motion-reduce:transition-none"></span></span>
</label>
</div>
</div>
</div>
<div class="grid grid-cols-3 gap-2 mb-4 max-[440px]:grid-cols-1">
<button type="button" autofocus data-tw07-close class="min-h-11 px-4 rounded-lg border border-tw07-line bg-tw07-well2 text-sm font-semibold text-tw07-ink cursor-pointer transition-colors duration-200 hover:bg-tw07-well3 focus-visible:outline-2 focus-visible:outline-tw07-ink focus-visible:outline-offset-2">Reject all</button>
<button type="button" data-tw07-close class="min-h-11 px-4 rounded-lg border border-tw07-line bg-tw07-well2 text-sm font-semibold text-tw07-ink cursor-pointer transition-colors duration-200 hover:bg-tw07-well3 focus-visible:outline-2 focus-visible:outline-tw07-ink focus-visible:outline-offset-2">Save preferences</button>
<button type="button" data-tw07-close class="min-h-11 px-4 rounded-lg border border-tw07-line bg-tw07-well2 text-sm font-semibold text-tw07-ink cursor-pointer transition-colors duration-200 hover:bg-tw07-well3 focus-visible:outline-2 focus-visible:outline-tw07-ink focus-visible:outline-offset-2">Accept all</button>
</div>
<p class="pt-3.5 border-t border-tw07-line text-xs text-tw07-sub">Read our <a href="#" class="text-tw07-ink underline">Cookie Policy</a> and <a href="#" class="text-tw07-ink underline">Privacy Policy</a>. You can change preferences anytime.</p>
</div>
</dialog>
</section><section class="tw-07 w-full min-h-svh grid place-items-center bg-tw07-bg font-tw07-sans px-6 py-10">
<button type="button" data-tw07-open class="inline-flex items-center justify-center min-h-11 px-6 rounded-lg border border-tw07-line bg-tw07-card text-[15px] font-semibold text-tw07-ink cursor-pointer transition-colors duration-200 hover:bg-tw07-well2 focus-visible:outline-2 focus-visible:outline-tw07-ink focus-visible:outline-offset-2">Cookie preferences</button>
<dialog id="tw-07-dlg" aria-labelledby="tw-07-title" class="m-auto w-[min(560px,94vw)] p-0 border-0 rounded-[14px] text-left text-tw07-ink bg-tw07-card shadow-[0_40px_60px_-20px_#0f172a59] opacity-0 transition-[opacity,overlay,display] duration-200 transition-discrete open:opacity-100 open:starting:opacity-0 open:backdrop:bg-tw07-scrim open:backdrop:backdrop-blur-[4px]">
<div class="relative px-8 pt-8 pb-6">
<button type="button" data-tw07-close aria-label="Close dialog" class="absolute top-3.5 right-3.5 grid place-items-center size-9 rounded-lg border-0 bg-transparent text-tw07-sub text-base cursor-pointer transition-colors duration-200 hover:bg-tw07-well2 hover:text-tw07-ink focus-visible:outline-2 focus-visible:outline-tw07-ink focus-visible:outline-offset-2">✕</button>
<h2 id="tw-07-title" class="mb-2 text-[1.35rem] font-extrabold tracking-[-0.015em] leading-tight">Cookie preferences</h2>
<p class="mb-5 text-sm leading-relaxed text-tw07-sub text-pretty">We use cookies to enhance your experience, analyze site traffic, and improve services. You can accept all, reject all non-essential, or customize per category.</p>
<div class="flex flex-col gap-2 mb-5 perspective-[900px]">
<div class="px-4 py-3.5 rounded-[9px] border border-tw07-line bg-tw07-well origin-bottom backface-hidden opacity-0 rotate-x-[-180deg] transition-[opacity,rotate] duration-700 ease-[cubic-bezier(.4,.15,.25,1)] in-[dialog[open]]:opacity-100 in-[dialog[open]]:rotate-x-0 [transition-delay:0ms] motion-reduce:transition-opacity motion-reduce:rotate-x-0 motion-reduce:[transition-delay:0ms]">
<div class="flex justify-between items-start gap-3.5">
<div>
<h3 class="mb-0.5 text-sm font-bold">Strictly necessary</h3>
<p class="text-xs leading-snug text-tw07-sub">Session cookies, CSRF tokens, load balancing. Cannot be disabled.</p>
</div>
<span class="shrink-0 grid place-items-center w-[42px] h-[22px] rounded-full bg-tw07-locked text-[0.62rem] font-extrabold tracking-wider text-white">ON</span>
</div>
</div>
<div class="px-4 py-3.5 rounded-[9px] border border-tw07-line bg-tw07-well origin-bottom backface-hidden opacity-0 rotate-x-[-180deg] transition-[opacity,rotate] duration-700 ease-[cubic-bezier(.4,.15,.25,1)] in-[dialog[open]]:opacity-100 in-[dialog[open]]:rotate-x-0 [transition-delay:180ms] motion-reduce:transition-opacity motion-reduce:rotate-x-0 motion-reduce:[transition-delay:0ms]">
<div class="flex justify-between items-start gap-3.5">
<div>
<h3 class="mb-0.5 text-sm font-bold">Analytics</h3>
<p class="text-xs leading-snug text-tw07-sub">Google Analytics, PostHog. Helps us understand aggregate usage patterns.</p>
</div>
<label class="shrink-0 cursor-pointer">
<input type="checkbox" checked class="peer sr-only">
<span class="sr-only">Allow analytics cookies</span>
<span aria-hidden="true" class="grid items-center w-[42px] h-[22px] rounded-full bg-tw07-off transition-colors duration-200 peer-checked:bg-tw07-good peer-focus-visible:outline-2 peer-focus-visible:outline-tw07-ink peer-focus-visible:outline-offset-2"><span class="block size-[18px] ml-0.5 rounded-full bg-white transition-transform duration-200 peer-checked:translate-x-5 motion-reduce:transition-none"></span></span>
</label>
</div>
</div>
<div class="px-4 py-3.5 rounded-[9px] border border-tw07-line bg-tw07-well origin-bottom backface-hidden opacity-0 rotate-x-[-180deg] transition-[opacity,rotate] duration-700 ease-[cubic-bezier(.4,.15,.25,1)] in-[dialog[open]]:opacity-100 in-[dialog[open]]:rotate-x-0 [transition-delay:360ms] motion-reduce:transition-opacity motion-reduce:rotate-x-0 motion-reduce:[transition-delay:0ms]">
<div class="flex justify-between items-start gap-3.5">
<div>
<h3 class="mb-0.5 text-sm font-bold">Marketing</h3>
<p class="text-xs leading-snug text-tw07-sub">Facebook Pixel, LinkedIn Insight Tag. Enables retargeting and personalized ads.</p>
</div>
<label class="shrink-0 cursor-pointer">
<input type="checkbox" class="peer sr-only">
<span class="sr-only">Allow marketing cookies</span>
<span aria-hidden="true" class="grid items-center w-[42px] h-[22px] rounded-full bg-tw07-off transition-colors duration-200 peer-checked:bg-tw07-good peer-focus-visible:outline-2 peer-focus-visible:outline-tw07-ink peer-focus-visible:outline-offset-2"><span class="block size-[18px] ml-0.5 rounded-full bg-white transition-transform duration-200 peer-checked:translate-x-5 motion-reduce:transition-none"></span></span>
</label>
</div>
</div>
<div class="px-4 py-3.5 rounded-[9px] border border-tw07-line bg-tw07-well origin-bottom backface-hidden opacity-0 rotate-x-[-180deg] transition-[opacity,rotate] duration-700 ease-[cubic-bezier(.4,.15,.25,1)] in-[dialog[open]]:opacity-100 in-[dialog[open]]:rotate-x-0 [transition-delay:540ms] motion-reduce:transition-opacity motion-reduce:rotate-x-0 motion-reduce:[transition-delay:0ms]">
<div class="flex justify-between items-start gap-3.5">
<div>
<h3 class="mb-0.5 text-sm font-bold">Personalization</h3>
<p class="text-xs leading-snug text-tw07-sub">Recommendation engine, saved preferences, theme mode. Improves your experience.</p>
</div>
<label class="shrink-0 cursor-pointer">
<input type="checkbox" checked class="peer sr-only">
<span class="sr-only">Allow personalization cookies</span>
<span aria-hidden="true" class="grid items-center w-[42px] h-[22px] rounded-full bg-tw07-off transition-colors duration-200 peer-checked:bg-tw07-good peer-focus-visible:outline-2 peer-focus-visible:outline-tw07-ink peer-focus-visible:outline-offset-2"><span class="block size-[18px] ml-0.5 rounded-full bg-white transition-transform duration-200 peer-checked:translate-x-5 motion-reduce:transition-none"></span></span>
</label>
</div>
</div>
</div>
<div class="grid grid-cols-3 gap-2 mb-4 max-[440px]:grid-cols-1">
<button type="button" autofocus data-tw07-close class="min-h-11 px-4 rounded-lg border border-tw07-line bg-tw07-well2 text-sm font-semibold text-tw07-ink cursor-pointer transition-colors duration-200 hover:bg-tw07-well3 focus-visible:outline-2 focus-visible:outline-tw07-ink focus-visible:outline-offset-2">Reject all</button>
<button type="button" data-tw07-close class="min-h-11 px-4 rounded-lg border border-tw07-line bg-tw07-well2 text-sm font-semibold text-tw07-ink cursor-pointer transition-colors duration-200 hover:bg-tw07-well3 focus-visible:outline-2 focus-visible:outline-tw07-ink focus-visible:outline-offset-2">Save preferences</button>
<button type="button" data-tw07-close class="min-h-11 px-4 rounded-lg border border-tw07-line bg-tw07-well2 text-sm font-semibold text-tw07-ink cursor-pointer transition-colors duration-200 hover:bg-tw07-well3 focus-visible:outline-2 focus-visible:outline-tw07-ink focus-visible:outline-offset-2">Accept all</button>
</div>
<p class="pt-3.5 border-t border-tw07-line text-xs text-tw07-sub">Read our <a href="#" class="text-tw07-ink underline">Cookie Policy</a> and <a href="#" class="text-tw07-ink underline">Privacy Policy</a>. You can change preferences anytime.</p>
</div>
</dialog>
</section>@import "tailwindcss";
@theme {
--color-tw07-bg: #f8fafc;
--color-tw07-bg: oklch(0.98 0.005 250);
--color-tw07-card: #ffffff;
--color-tw07-card: oklch(1 0 0);
--color-tw07-well: #fafbfc;
--color-tw07-well: oklch(0.98 0.003 255);
--color-tw07-well2: #f1f5f9;
--color-tw07-well2: oklch(0.96 0.006 250);
--color-tw07-well3: #e2e8f0;
--color-tw07-well3: oklch(0.92 0.01 250);
--color-tw07-ink: #1e293b;
--color-tw07-ink: oklch(0.3 0.03 258);
--color-tw07-sub: #55657c;
--color-tw07-sub: oklch(0.5 0.03 255);
--color-tw07-line: #e2e8f0;
--color-tw07-line: oklch(0.92 0.01 250);
--color-tw07-good: #059669;
--color-tw07-good: oklch(0.63 0.14 163);
--color-tw07-off: #94a3b8;
--color-tw07-off: oklch(0.7 0.03 255);
--color-tw07-locked: #64748b;
--color-tw07-locked: oklch(0.55 0.03 255);
--color-tw07-scrim: #0f172a99;
--color-tw07-scrim: oklch(0.23 0.04 265 / 0.6);
--font-tw07-sans: 'Outfit', system-ui, sans-serif;
}
.tw-07 {
width: 100%;
}
#tw-07-dlg::backdrop {
background: oklch(0.23 0.04 265 / 0);
backdrop-filter: blur(0px);
transition: background .2s ease, backdrop-filter .2s ease, overlay .2s allow-discrete, display .2s allow-discrete;
}
@starting-style {
#tw-07-dlg[open]::backdrop {
background: oklch(0.23 0.04 265 / 0);
backdrop-filter: blur(0px);
}
}
@media (prefers-reduced-motion: reduce) {
#tw-07-dlg::backdrop {
transition-duration: .001s;
}
}@import "tailwindcss";
@theme {
--color-tw07-bg: #f8fafc;
--color-tw07-bg: oklch(0.98 0.005 250);
--color-tw07-card: #ffffff;
--color-tw07-card: oklch(1 0 0);
--color-tw07-well: #fafbfc;
--color-tw07-well: oklch(0.98 0.003 255);
--color-tw07-well2: #f1f5f9;
--color-tw07-well2: oklch(0.96 0.006 250);
--color-tw07-well3: #e2e8f0;
--color-tw07-well3: oklch(0.92 0.01 250);
--color-tw07-ink: #1e293b;
--color-tw07-ink: oklch(0.3 0.03 258);
--color-tw07-sub: #55657c;
--color-tw07-sub: oklch(0.5 0.03 255);
--color-tw07-line: #e2e8f0;
--color-tw07-line: oklch(0.92 0.01 250);
--color-tw07-good: #059669;
--color-tw07-good: oklch(0.63 0.14 163);
--color-tw07-off: #94a3b8;
--color-tw07-off: oklch(0.7 0.03 255);
--color-tw07-locked: #64748b;
--color-tw07-locked: oklch(0.55 0.03 255);
--color-tw07-scrim: #0f172a99;
--color-tw07-scrim: oklch(0.23 0.04 265 / 0.6);
--font-tw07-sans: 'Outfit', system-ui, sans-serif;
}
.tw-07 {
width: 100%;
}
#tw-07-dlg::backdrop {
background: oklch(0.23 0.04 265 / 0);
backdrop-filter: blur(0px);
transition: background .2s ease, backdrop-filter .2s ease, overlay .2s allow-discrete, display .2s allow-discrete;
}
@starting-style {
#tw-07-dlg[open]::backdrop {
background: oklch(0.23 0.04 265 / 0);
backdrop-filter: blur(0px);
}
}
@media (prefers-reduced-motion: reduce) {
#tw-07-dlg::backdrop {
transition-duration: .001s;
}
}(function () {
var root = document.querySelector('.tw-07');
var dlg = root.querySelector('#tw-07-dlg');
var card = dlg.firstElementChild;
root.querySelector('[data-tw07-open]').addEventListener('click', function () { dlg.showModal(); });
root.querySelectorAll('[data-tw07-close]').forEach(function (b) {
// Production: read the toggles here and gate your script loader on the result.
b.addEventListener('click', function () { dlg.close(); });
});
dlg.addEventListener('click', function (e) {
var r = card.getBoundingClientRect();
if (e.clientX < r.left || e.clientX > r.right || e.clientY < r.top || e.clientY > r.bottom) dlg.close();
});
})();(function () {
var root = document.querySelector('.tw-07');
var dlg = root.querySelector('#tw-07-dlg');
var card = dlg.firstElementChild;
root.querySelector('[data-tw07-open]').addEventListener('click', function () { dlg.showModal(); });
root.querySelectorAll('[data-tw07-close]').forEach(function (b) {
// Production: read the toggles here and gate your script loader on the result.
b.addEventListener('click', function () { dlg.close(); });
});
dlg.addEventListener('click', function (e) {
var r = card.getBoundingClientRect();
if (e.clientX < r.left || e.clientX > r.right || e.clientY < r.top || e.clientY > r.bottom) dlg.close();
});
})();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-tw07-bg: #f8fafc;
--color-tw07-bg: oklch(0.98 0.005 250);
--color-tw07-card: #ffffff;
--color-tw07-card: oklch(1 0 0);
--color-tw07-well: #fafbfc;
--color-tw07-well: oklch(0.98 0.003 255);
--color-tw07-well2: #f1f5f9;
--color-tw07-well2: oklch(0.96 0.006 250);
--color-tw07-well3: #e2e8f0;
--color-tw07-well3: oklch(0.92 0.01 250);
--color-tw07-ink: #1e293b;
--color-tw07-ink: oklch(0.3 0.03 258);
--color-tw07-sub: #55657c;
--color-tw07-sub: oklch(0.5 0.03 255);
--color-tw07-line: #e2e8f0;
--color-tw07-line: oklch(0.92 0.01 250);
--color-tw07-good: #059669;
--color-tw07-good: oklch(0.63 0.14 163);
--color-tw07-off: #94a3b8;
--color-tw07-off: oklch(0.7 0.03 255);
--color-tw07-locked: #64748b;
--color-tw07-locked: oklch(0.55 0.03 255);
--color-tw07-scrim: #0f172a99;
--color-tw07-scrim: oklch(0.23 0.04 265 / 0.6);
--font-tw07-sans: 'Outfit', system-ui, sans-serif;
}
.tw-07 {
width: 100%;
}
#tw-07-dlg::backdrop {
background: oklch(0.23 0.04 265 / 0);
backdrop-filter: blur(0px);
transition: background .2s ease, backdrop-filter .2s ease, overlay .2s allow-discrete, display .2s allow-discrete;
}
@starting-style {
#tw-07-dlg[open]::backdrop {
background: oklch(0.23 0.04 265 / 0);
backdrop-filter: blur(0px);
}
}
@media (prefers-reduced-motion: reduce) {
#tw-07-dlg::backdrop {
transition-duration: .001s;
}
}@import "tailwindcss";
@theme {
--color-tw07-bg: #f8fafc;
--color-tw07-bg: oklch(0.98 0.005 250);
--color-tw07-card: #ffffff;
--color-tw07-card: oklch(1 0 0);
--color-tw07-well: #fafbfc;
--color-tw07-well: oklch(0.98 0.003 255);
--color-tw07-well2: #f1f5f9;
--color-tw07-well2: oklch(0.96 0.006 250);
--color-tw07-well3: #e2e8f0;
--color-tw07-well3: oklch(0.92 0.01 250);
--color-tw07-ink: #1e293b;
--color-tw07-ink: oklch(0.3 0.03 258);
--color-tw07-sub: #55657c;
--color-tw07-sub: oklch(0.5 0.03 255);
--color-tw07-line: #e2e8f0;
--color-tw07-line: oklch(0.92 0.01 250);
--color-tw07-good: #059669;
--color-tw07-good: oklch(0.63 0.14 163);
--color-tw07-off: #94a3b8;
--color-tw07-off: oklch(0.7 0.03 255);
--color-tw07-locked: #64748b;
--color-tw07-locked: oklch(0.55 0.03 255);
--color-tw07-scrim: #0f172a99;
--color-tw07-scrim: oklch(0.23 0.04 265 / 0.6);
--font-tw07-sans: 'Outfit', system-ui, sans-serif;
}
.tw-07 {
width: 100%;
}
#tw-07-dlg::backdrop {
background: oklch(0.23 0.04 265 / 0);
backdrop-filter: blur(0px);
transition: background .2s ease, backdrop-filter .2s ease, overlay .2s allow-discrete, display .2s allow-discrete;
}
@starting-style {
#tw-07-dlg[open]::backdrop {
background: oklch(0.23 0.04 265 / 0);
backdrop-filter: blur(0px);
}
}
@media (prefers-reduced-motion: reduce) {
#tw-07-dlg::backdrop {
transition-duration: .001s;
}
}<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=Outfit:wght@400;500;600;700;800&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=Outfit:wght@400;500;600;700;800&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- The original needed an
.is-unfoldingclass added on the next animation frame, plus a comment explaining the race. Readingin-[dialog[open]]:removes both: the resting state exists from page load and the open state is the browser's own attribute. - Four
nth-childdelay rules become four[transition-delay:…]utilities on the rows they delay — insert a fifth category and its timing travels with it. - The toggles are
sr-onlycheckboxes with apeer-checked:track. Native control, native keyboard, no ARIA to keep in sync, and ansr-onlylabel so each switch says which category it governs. - The three actions share one grid track and one style. Equal weight is the structure, not a promise in a comment.
- UI change from the CSS original: all three actions are 44px and wrap to a single column under 440px, and the locked Necessary pill uses a slate fill so it does not read as an active green toggle.
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 GDPR Cookie Preferences Modal with Unfolding Category Rows
Source: https://codefronts.com/components/tailwind-css-modals/tailwind-gdpr-cookie-preferences-modal-unfolding-rows/
A consent centre whose four category rows unfold downward like origami while the title and the three actions are readable from frame one — the fast path stays fast. The port drives the cascade from the dialog's own [open] state with in-[dialog[open]]: plus per-row [transition-delay:…], and builds the toggles from peer-checked: instead of custom controls.
## HTML
```html
<section class="tw-07 w-full min-h-svh grid place-items-center bg-tw07-bg font-tw07-sans px-6 py-10">
<button type="button" data-tw07-open class="inline-flex items-center justify-center min-h-11 px-6 rounded-lg border border-tw07-line bg-tw07-card text-[15px] font-semibold text-tw07-ink cursor-pointer transition-colors duration-200 hover:bg-tw07-well2 focus-visible:outline-2 focus-visible:outline-tw07-ink focus-visible:outline-offset-2">Cookie preferences</button>
<dialog id="tw-07-dlg" aria-labelledby="tw-07-title" class="m-auto w-[min(560px,94vw)] p-0 border-0 rounded-[14px] text-left text-tw07-ink bg-tw07-card shadow-[0_40px_60px_-20px_#0f172a59] opacity-0 transition-[opacity,overlay,display] duration-200 transition-discrete open:opacity-100 open:starting:opacity-0 open:backdrop:bg-tw07-scrim open:backdrop:backdrop-blur-[4px]">
<div class="relative px-8 pt-8 pb-6">
<button type="button" data-tw07-close aria-label="Close dialog" class="absolute top-3.5 right-3.5 grid place-items-center size-9 rounded-lg border-0 bg-transparent text-tw07-sub text-base cursor-pointer transition-colors duration-200 hover:bg-tw07-well2 hover:text-tw07-ink focus-visible:outline-2 focus-visible:outline-tw07-ink focus-visible:outline-offset-2">✕</button>
<h2 id="tw-07-title" class="mb-2 text-[1.35rem] font-extrabold tracking-[-0.015em] leading-tight">Cookie preferences</h2>
<p class="mb-5 text-sm leading-relaxed text-tw07-sub text-pretty">We use cookies to enhance your experience, analyze site traffic, and improve services. You can accept all, reject all non-essential, or customize per category.</p>
<div class="flex flex-col gap-2 mb-5 perspective-[900px]">
<div class="px-4 py-3.5 rounded-[9px] border border-tw07-line bg-tw07-well origin-bottom backface-hidden opacity-0 rotate-x-[-180deg] transition-[opacity,rotate] duration-700 ease-[cubic-bezier(.4,.15,.25,1)] in-[dialog[open]]:opacity-100 in-[dialog[open]]:rotate-x-0 [transition-delay:0ms] motion-reduce:transition-opacity motion-reduce:rotate-x-0 motion-reduce:[transition-delay:0ms]">
<div class="flex justify-between items-start gap-3.5">
<div>
<h3 class="mb-0.5 text-sm font-bold">Strictly necessary</h3>
<p class="text-xs leading-snug text-tw07-sub">Session cookies, CSRF tokens, load balancing. Cannot be disabled.</p>
</div>
<span class="shrink-0 grid place-items-center w-[42px] h-[22px] rounded-full bg-tw07-locked text-[0.62rem] font-extrabold tracking-wider text-white">ON</span>
</div>
</div>
<div class="px-4 py-3.5 rounded-[9px] border border-tw07-line bg-tw07-well origin-bottom backface-hidden opacity-0 rotate-x-[-180deg] transition-[opacity,rotate] duration-700 ease-[cubic-bezier(.4,.15,.25,1)] in-[dialog[open]]:opacity-100 in-[dialog[open]]:rotate-x-0 [transition-delay:180ms] motion-reduce:transition-opacity motion-reduce:rotate-x-0 motion-reduce:[transition-delay:0ms]">
<div class="flex justify-between items-start gap-3.5">
<div>
<h3 class="mb-0.5 text-sm font-bold">Analytics</h3>
<p class="text-xs leading-snug text-tw07-sub">Google Analytics, PostHog. Helps us understand aggregate usage patterns.</p>
</div>
<label class="shrink-0 cursor-pointer">
<input type="checkbox" checked class="peer sr-only">
<span class="sr-only">Allow analytics cookies</span>
<span aria-hidden="true" class="grid items-center w-[42px] h-[22px] rounded-full bg-tw07-off transition-colors duration-200 peer-checked:bg-tw07-good peer-focus-visible:outline-2 peer-focus-visible:outline-tw07-ink peer-focus-visible:outline-offset-2"><span class="block size-[18px] ml-0.5 rounded-full bg-white transition-transform duration-200 peer-checked:translate-x-5 motion-reduce:transition-none"></span></span>
</label>
</div>
</div>
<div class="px-4 py-3.5 rounded-[9px] border border-tw07-line bg-tw07-well origin-bottom backface-hidden opacity-0 rotate-x-[-180deg] transition-[opacity,rotate] duration-700 ease-[cubic-bezier(.4,.15,.25,1)] in-[dialog[open]]:opacity-100 in-[dialog[open]]:rotate-x-0 [transition-delay:360ms] motion-reduce:transition-opacity motion-reduce:rotate-x-0 motion-reduce:[transition-delay:0ms]">
<div class="flex justify-between items-start gap-3.5">
<div>
<h3 class="mb-0.5 text-sm font-bold">Marketing</h3>
<p class="text-xs leading-snug text-tw07-sub">Facebook Pixel, LinkedIn Insight Tag. Enables retargeting and personalized ads.</p>
</div>
<label class="shrink-0 cursor-pointer">
<input type="checkbox" class="peer sr-only">
<span class="sr-only">Allow marketing cookies</span>
<span aria-hidden="true" class="grid items-center w-[42px] h-[22px] rounded-full bg-tw07-off transition-colors duration-200 peer-checked:bg-tw07-good peer-focus-visible:outline-2 peer-focus-visible:outline-tw07-ink peer-focus-visible:outline-offset-2"><span class="block size-[18px] ml-0.5 rounded-full bg-white transition-transform duration-200 peer-checked:translate-x-5 motion-reduce:transition-none"></span></span>
</label>
</div>
</div>
<div class="px-4 py-3.5 rounded-[9px] border border-tw07-line bg-tw07-well origin-bottom backface-hidden opacity-0 rotate-x-[-180deg] transition-[opacity,rotate] duration-700 ease-[cubic-bezier(.4,.15,.25,1)] in-[dialog[open]]:opacity-100 in-[dialog[open]]:rotate-x-0 [transition-delay:540ms] motion-reduce:transition-opacity motion-reduce:rotate-x-0 motion-reduce:[transition-delay:0ms]">
<div class="flex justify-between items-start gap-3.5">
<div>
<h3 class="mb-0.5 text-sm font-bold">Personalization</h3>
<p class="text-xs leading-snug text-tw07-sub">Recommendation engine, saved preferences, theme mode. Improves your experience.</p>
</div>
<label class="shrink-0 cursor-pointer">
<input type="checkbox" checked class="peer sr-only">
<span class="sr-only">Allow personalization cookies</span>
<span aria-hidden="true" class="grid items-center w-[42px] h-[22px] rounded-full bg-tw07-off transition-colors duration-200 peer-checked:bg-tw07-good peer-focus-visible:outline-2 peer-focus-visible:outline-tw07-ink peer-focus-visible:outline-offset-2"><span class="block size-[18px] ml-0.5 rounded-full bg-white transition-transform duration-200 peer-checked:translate-x-5 motion-reduce:transition-none"></span></span>
</label>
</div>
</div>
</div>
<div class="grid grid-cols-3 gap-2 mb-4 max-[440px]:grid-cols-1">
<button type="button" autofocus data-tw07-close class="min-h-11 px-4 rounded-lg border border-tw07-line bg-tw07-well2 text-sm font-semibold text-tw07-ink cursor-pointer transition-colors duration-200 hover:bg-tw07-well3 focus-visible:outline-2 focus-visible:outline-tw07-ink focus-visible:outline-offset-2">Reject all</button>
<button type="button" data-tw07-close class="min-h-11 px-4 rounded-lg border border-tw07-line bg-tw07-well2 text-sm font-semibold text-tw07-ink cursor-pointer transition-colors duration-200 hover:bg-tw07-well3 focus-visible:outline-2 focus-visible:outline-tw07-ink focus-visible:outline-offset-2">Save preferences</button>
<button type="button" data-tw07-close class="min-h-11 px-4 rounded-lg border border-tw07-line bg-tw07-well2 text-sm font-semibold text-tw07-ink cursor-pointer transition-colors duration-200 hover:bg-tw07-well3 focus-visible:outline-2 focus-visible:outline-tw07-ink focus-visible:outline-offset-2">Accept all</button>
</div>
<p class="pt-3.5 border-t border-tw07-line text-xs text-tw07-sub">Read our <a href="#" class="text-tw07-ink underline">Cookie Policy</a> and <a href="#" class="text-tw07-ink underline">Privacy Policy</a>. You can change preferences anytime.</p>
</div>
</dialog>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tw07-bg: #f8fafc;
--color-tw07-bg: oklch(0.98 0.005 250);
--color-tw07-card: #ffffff;
--color-tw07-card: oklch(1 0 0);
--color-tw07-well: #fafbfc;
--color-tw07-well: oklch(0.98 0.003 255);
--color-tw07-well2: #f1f5f9;
--color-tw07-well2: oklch(0.96 0.006 250);
--color-tw07-well3: #e2e8f0;
--color-tw07-well3: oklch(0.92 0.01 250);
--color-tw07-ink: #1e293b;
--color-tw07-ink: oklch(0.3 0.03 258);
--color-tw07-sub: #55657c;
--color-tw07-sub: oklch(0.5 0.03 255);
--color-tw07-line: #e2e8f0;
--color-tw07-line: oklch(0.92 0.01 250);
--color-tw07-good: #059669;
--color-tw07-good: oklch(0.63 0.14 163);
--color-tw07-off: #94a3b8;
--color-tw07-off: oklch(0.7 0.03 255);
--color-tw07-locked: #64748b;
--color-tw07-locked: oklch(0.55 0.03 255);
--color-tw07-scrim: #0f172a99;
--color-tw07-scrim: oklch(0.23 0.04 265 / 0.6);
--font-tw07-sans: 'Outfit', system-ui, sans-serif;
}
.tw-07 {
width: 100%;
}
#tw-07-dlg::backdrop {
background: oklch(0.23 0.04 265 / 0);
backdrop-filter: blur(0px);
transition: background .2s ease, backdrop-filter .2s ease, overlay .2s allow-discrete, display .2s allow-discrete;
}
@starting-style {
#tw-07-dlg[open]::backdrop {
background: oklch(0.23 0.04 265 / 0);
backdrop-filter: blur(0px);
}
}
@media (prefers-reduced-motion: reduce) {
#tw-07-dlg::backdrop {
transition-duration: .001s;
}
}
```
## JavaScript
```js
(function () {
var root = document.querySelector('.tw-07');
var dlg = root.querySelector('#tw-07-dlg');
var card = dlg.firstElementChild;
root.querySelector('[data-tw07-open]').addEventListener('click', function () { dlg.showModal(); });
root.querySelectorAll('[data-tw07-close]').forEach(function (b) {
// Production: read the toggles here and gate your script loader on the result.
b.addEventListener('click', function () { dlg.close(); });
});
dlg.addEventListener('click', function (e) {
var r = card.getBoundingClientRect();
if (e.clientX < r.left || e.clientX > r.right || e.clientY < r.top || e.clientY > r.bottom) dlg.close();
});
})();
```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 GDPR Cookie Preferences Modal with Unfolding Category Rows
Source: https://codefronts.com/components/tailwind-css-modals/tailwind-gdpr-cookie-preferences-modal-unfolding-rows/
A consent centre whose four category rows unfold downward like origami while the title and the three actions are readable from frame one — the fast path stays fast. The port drives the cascade from the dialog's own [open] state with in-[dialog[open]]: plus per-row [transition-delay:…], and builds the toggles from peer-checked: instead of custom controls.
## HTML
```html
<section class="tw-07 w-full min-h-svh grid place-items-center bg-tw07-bg font-tw07-sans px-6 py-10">
<button type="button" data-tw07-open class="inline-flex items-center justify-center min-h-11 px-6 rounded-lg border border-tw07-line bg-tw07-card text-[15px] font-semibold text-tw07-ink cursor-pointer transition-colors duration-200 hover:bg-tw07-well2 focus-visible:outline-2 focus-visible:outline-tw07-ink focus-visible:outline-offset-2">Cookie preferences</button>
<dialog id="tw-07-dlg" aria-labelledby="tw-07-title" class="m-auto w-[min(560px,94vw)] p-0 border-0 rounded-[14px] text-left text-tw07-ink bg-tw07-card shadow-[0_40px_60px_-20px_#0f172a59] opacity-0 transition-[opacity,overlay,display] duration-200 transition-discrete open:opacity-100 open:starting:opacity-0 open:backdrop:bg-tw07-scrim open:backdrop:backdrop-blur-[4px]">
<div class="relative px-8 pt-8 pb-6">
<button type="button" data-tw07-close aria-label="Close dialog" class="absolute top-3.5 right-3.5 grid place-items-center size-9 rounded-lg border-0 bg-transparent text-tw07-sub text-base cursor-pointer transition-colors duration-200 hover:bg-tw07-well2 hover:text-tw07-ink focus-visible:outline-2 focus-visible:outline-tw07-ink focus-visible:outline-offset-2">✕</button>
<h2 id="tw-07-title" class="mb-2 text-[1.35rem] font-extrabold tracking-[-0.015em] leading-tight">Cookie preferences</h2>
<p class="mb-5 text-sm leading-relaxed text-tw07-sub text-pretty">We use cookies to enhance your experience, analyze site traffic, and improve services. You can accept all, reject all non-essential, or customize per category.</p>
<div class="flex flex-col gap-2 mb-5 perspective-[900px]">
<div class="px-4 py-3.5 rounded-[9px] border border-tw07-line bg-tw07-well origin-bottom backface-hidden opacity-0 rotate-x-[-180deg] transition-[opacity,rotate] duration-700 ease-[cubic-bezier(.4,.15,.25,1)] in-[dialog[open]]:opacity-100 in-[dialog[open]]:rotate-x-0 [transition-delay:0ms] motion-reduce:transition-opacity motion-reduce:rotate-x-0 motion-reduce:[transition-delay:0ms]">
<div class="flex justify-between items-start gap-3.5">
<div>
<h3 class="mb-0.5 text-sm font-bold">Strictly necessary</h3>
<p class="text-xs leading-snug text-tw07-sub">Session cookies, CSRF tokens, load balancing. Cannot be disabled.</p>
</div>
<span class="shrink-0 grid place-items-center w-[42px] h-[22px] rounded-full bg-tw07-locked text-[0.62rem] font-extrabold tracking-wider text-white">ON</span>
</div>
</div>
<div class="px-4 py-3.5 rounded-[9px] border border-tw07-line bg-tw07-well origin-bottom backface-hidden opacity-0 rotate-x-[-180deg] transition-[opacity,rotate] duration-700 ease-[cubic-bezier(.4,.15,.25,1)] in-[dialog[open]]:opacity-100 in-[dialog[open]]:rotate-x-0 [transition-delay:180ms] motion-reduce:transition-opacity motion-reduce:rotate-x-0 motion-reduce:[transition-delay:0ms]">
<div class="flex justify-between items-start gap-3.5">
<div>
<h3 class="mb-0.5 text-sm font-bold">Analytics</h3>
<p class="text-xs leading-snug text-tw07-sub">Google Analytics, PostHog. Helps us understand aggregate usage patterns.</p>
</div>
<label class="shrink-0 cursor-pointer">
<input type="checkbox" checked class="peer sr-only">
<span class="sr-only">Allow analytics cookies</span>
<span aria-hidden="true" class="grid items-center w-[42px] h-[22px] rounded-full bg-tw07-off transition-colors duration-200 peer-checked:bg-tw07-good peer-focus-visible:outline-2 peer-focus-visible:outline-tw07-ink peer-focus-visible:outline-offset-2"><span class="block size-[18px] ml-0.5 rounded-full bg-white transition-transform duration-200 peer-checked:translate-x-5 motion-reduce:transition-none"></span></span>
</label>
</div>
</div>
<div class="px-4 py-3.5 rounded-[9px] border border-tw07-line bg-tw07-well origin-bottom backface-hidden opacity-0 rotate-x-[-180deg] transition-[opacity,rotate] duration-700 ease-[cubic-bezier(.4,.15,.25,1)] in-[dialog[open]]:opacity-100 in-[dialog[open]]:rotate-x-0 [transition-delay:360ms] motion-reduce:transition-opacity motion-reduce:rotate-x-0 motion-reduce:[transition-delay:0ms]">
<div class="flex justify-between items-start gap-3.5">
<div>
<h3 class="mb-0.5 text-sm font-bold">Marketing</h3>
<p class="text-xs leading-snug text-tw07-sub">Facebook Pixel, LinkedIn Insight Tag. Enables retargeting and personalized ads.</p>
</div>
<label class="shrink-0 cursor-pointer">
<input type="checkbox" class="peer sr-only">
<span class="sr-only">Allow marketing cookies</span>
<span aria-hidden="true" class="grid items-center w-[42px] h-[22px] rounded-full bg-tw07-off transition-colors duration-200 peer-checked:bg-tw07-good peer-focus-visible:outline-2 peer-focus-visible:outline-tw07-ink peer-focus-visible:outline-offset-2"><span class="block size-[18px] ml-0.5 rounded-full bg-white transition-transform duration-200 peer-checked:translate-x-5 motion-reduce:transition-none"></span></span>
</label>
</div>
</div>
<div class="px-4 py-3.5 rounded-[9px] border border-tw07-line bg-tw07-well origin-bottom backface-hidden opacity-0 rotate-x-[-180deg] transition-[opacity,rotate] duration-700 ease-[cubic-bezier(.4,.15,.25,1)] in-[dialog[open]]:opacity-100 in-[dialog[open]]:rotate-x-0 [transition-delay:540ms] motion-reduce:transition-opacity motion-reduce:rotate-x-0 motion-reduce:[transition-delay:0ms]">
<div class="flex justify-between items-start gap-3.5">
<div>
<h3 class="mb-0.5 text-sm font-bold">Personalization</h3>
<p class="text-xs leading-snug text-tw07-sub">Recommendation engine, saved preferences, theme mode. Improves your experience.</p>
</div>
<label class="shrink-0 cursor-pointer">
<input type="checkbox" checked class="peer sr-only">
<span class="sr-only">Allow personalization cookies</span>
<span aria-hidden="true" class="grid items-center w-[42px] h-[22px] rounded-full bg-tw07-off transition-colors duration-200 peer-checked:bg-tw07-good peer-focus-visible:outline-2 peer-focus-visible:outline-tw07-ink peer-focus-visible:outline-offset-2"><span class="block size-[18px] ml-0.5 rounded-full bg-white transition-transform duration-200 peer-checked:translate-x-5 motion-reduce:transition-none"></span></span>
</label>
</div>
</div>
</div>
<div class="grid grid-cols-3 gap-2 mb-4 max-[440px]:grid-cols-1">
<button type="button" autofocus data-tw07-close class="min-h-11 px-4 rounded-lg border border-tw07-line bg-tw07-well2 text-sm font-semibold text-tw07-ink cursor-pointer transition-colors duration-200 hover:bg-tw07-well3 focus-visible:outline-2 focus-visible:outline-tw07-ink focus-visible:outline-offset-2">Reject all</button>
<button type="button" data-tw07-close class="min-h-11 px-4 rounded-lg border border-tw07-line bg-tw07-well2 text-sm font-semibold text-tw07-ink cursor-pointer transition-colors duration-200 hover:bg-tw07-well3 focus-visible:outline-2 focus-visible:outline-tw07-ink focus-visible:outline-offset-2">Save preferences</button>
<button type="button" data-tw07-close class="min-h-11 px-4 rounded-lg border border-tw07-line bg-tw07-well2 text-sm font-semibold text-tw07-ink cursor-pointer transition-colors duration-200 hover:bg-tw07-well3 focus-visible:outline-2 focus-visible:outline-tw07-ink focus-visible:outline-offset-2">Accept all</button>
</div>
<p class="pt-3.5 border-t border-tw07-line text-xs text-tw07-sub">Read our <a href="#" class="text-tw07-ink underline">Cookie Policy</a> and <a href="#" class="text-tw07-ink underline">Privacy Policy</a>. You can change preferences anytime.</p>
</div>
</dialog>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tw07-bg: #f8fafc;
--color-tw07-bg: oklch(0.98 0.005 250);
--color-tw07-card: #ffffff;
--color-tw07-card: oklch(1 0 0);
--color-tw07-well: #fafbfc;
--color-tw07-well: oklch(0.98 0.003 255);
--color-tw07-well2: #f1f5f9;
--color-tw07-well2: oklch(0.96 0.006 250);
--color-tw07-well3: #e2e8f0;
--color-tw07-well3: oklch(0.92 0.01 250);
--color-tw07-ink: #1e293b;
--color-tw07-ink: oklch(0.3 0.03 258);
--color-tw07-sub: #55657c;
--color-tw07-sub: oklch(0.5 0.03 255);
--color-tw07-line: #e2e8f0;
--color-tw07-line: oklch(0.92 0.01 250);
--color-tw07-good: #059669;
--color-tw07-good: oklch(0.63 0.14 163);
--color-tw07-off: #94a3b8;
--color-tw07-off: oklch(0.7 0.03 255);
--color-tw07-locked: #64748b;
--color-tw07-locked: oklch(0.55 0.03 255);
--color-tw07-scrim: #0f172a99;
--color-tw07-scrim: oklch(0.23 0.04 265 / 0.6);
--font-tw07-sans: 'Outfit', system-ui, sans-serif;
}
.tw-07 {
width: 100%;
}
#tw-07-dlg::backdrop {
background: oklch(0.23 0.04 265 / 0);
backdrop-filter: blur(0px);
transition: background .2s ease, backdrop-filter .2s ease, overlay .2s allow-discrete, display .2s allow-discrete;
}
@starting-style {
#tw-07-dlg[open]::backdrop {
background: oklch(0.23 0.04 265 / 0);
backdrop-filter: blur(0px);
}
}
@media (prefers-reduced-motion: reduce) {
#tw-07-dlg::backdrop {
transition-duration: .001s;
}
}
```
## JavaScript
```js
(function () {
var root = document.querySelector('.tw-07');
var dlg = root.querySelector('#tw-07-dlg');
var card = dlg.firstElementChild;
root.querySelector('[data-tw07-open]').addEventListener('click', function () { dlg.showModal(); });
root.querySelectorAll('[data-tw07-close]').forEach(function (b) {
// Production: read the toggles here and gate your script loader on the result.
b.addEventListener('click', function () { dlg.close(); });
});
dlg.addEventListener('click', function (e) {
var r = card.getBoundingClientRect();
if (e.clientX < r.left || e.clientX > r.right || e.clientY < r.top || e.clientY > r.bottom) dlg.close();
});
})();
```How this works
Actions first, theatre second. The header and the three buttons carry no entrance at all; only the category rows animate. A visitor who wants Reject all can click it in the first frame, which is both the kind thing and the compliant thing.
The cascade reads the dialog. Each row transitions from rotate-x-[-180deg] to flat when an ancestor dialog is [open], so there is no second state class to add on the next frame and no rAF race to get wrong.
Equal weight is enforced by the grid. Accept all, Reject all and Save preferences share one grid-cols-3 track with the same sizing; the only difference is fill, and it is not applied to Accept.
Make it yours
- For CCPA, replace Reject all with the verbatim "Do Not Sell or Share My Personal Info" string — synonyms do not satisfy the statute.
- Pre-decline analytics and marketing when
navigator.globalPrivacyControlis true, before the dialog is shown. - Replace the generic categories with your cookie audit, one row per category with its retention period.
- Re-prompt when the stored consent timestamp is older than twelve months.
Gotchas — read before shipping
- A banner is not compliance. Non-essential scripts must not run until the visitor has chosen; a preferences UI in front of already-firing analytics is the most common GDPR failure.
- Accept and Reject must look the same. Regulators have fined exactly the design where Accept is filled and Reject is a faint link.
- The Necessary row is immutable — a locked state, not an unchecked toggle the visitor can flip.
- The unfold needs
perspective-*on the ancestor. Without itrotate-xcollapses to a flat squash and the metaphor disappears. - Never delay the primary actions behind the cascade. Decorative motion in front of a legal choice is a dark pattern by another name.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ · 117+ anim | 16.4+ · 17.5+ anim | 128+ · 129+ anim | 111+ · 117+ anim |
3D transforms are Baseline 2016; only the dialog's @starting-style fade is on the newer gate and degrades to an instant show.