13 / 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 Consent Banner with details Disclosure
A corner consent sheet with equal-weight Accept and Reject, a <details> preferences panel and real toggle switches. The port deletes state-tracking JavaScript twice over: the disclosure becomes a <details> read by open:, and the slide-in becomes data-in read by data-[in]:.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tm-13 relative w-full min-h-svh px-8 py-8 bg-tm13-bg font-tm13-sans">
<div class="min-h-[calc(100svh-64px)] grid place-items-center gap-4 content-center">
<p class="font-tm13-mono text-[13px] tracking-[0.1em] text-tm13-faint">Demo content behind the banner — the page stays usable.</p>
<button type="button" data-tm13-reopen hidden class="inline-flex items-center justify-center min-h-11 px-5 rounded-xl border border-tm13-edge bg-tm13-panel text-[13px] font-semibold text-tm13-ink cursor-pointer transition-colors duration-200 hover:border-tm13-acc focus-visible:outline-2 focus-visible:outline-tm13-acc focus-visible:outline-offset-2">Cookie settings</button>
</div>
<section data-tm13-banner role="region" aria-label="Cookie consent" class="fixed left-6 bottom-6 z-10 w-[min(420px,calc(100vw-48px))] rounded-2xl bg-tm13-card border border-tm13-cardedge shadow-[0_30px_70px_-24px_#000] translate-y-[140%] opacity-0 transition-[translate,opacity] duration-[400ms] ease-[cubic-bezier(0.4,0,0.2,1)] data-[in]:translate-y-0 data-[in]:opacity-100 motion-reduce:transition-none">
<div class="px-5 pt-5 pb-5">
<h2 class="mb-2 text-[17px] font-bold text-tm13-hi">We value your privacy</h2>
<p class="mb-4 text-[13px] leading-relaxed text-tm13-mut">We use cookies to run the site and, with your consent, to analyse traffic and personalise content. <a href="#privacy" class="text-tm13-acc no-underline hover:underline focus-visible:outline-2 focus-visible:outline-tm13-acc focus-visible:outline-offset-2">Cookie Policy</a>.</p>
<details data-tm13-prefs class="group mb-4 border-t border-tm13-edge">
<summary class="flex items-center justify-between gap-3 min-h-11 list-none cursor-pointer text-[13px] font-semibold text-tm13-ink focus-visible:outline-2 focus-visible:outline-tm13-acc focus-visible:outline-offset-2 rounded-md">
Preferences
<span aria-hidden="true" class="text-tm13-mut transition-transform duration-200 group-open:rotate-180 motion-reduce:transition-none">▾</span>
</summary>
<div class="flex flex-col gap-0.5 pb-1">
<label class="flex items-center justify-between gap-4 py-2.5 cursor-not-allowed">
<input type="checkbox" checked disabled aria-label="Strictly necessary (always on)" class="peer order-2 shrink-0 relative w-11 h-6 appearance-none rounded-full bg-tm13-acc opacity-70 cursor-not-allowed before:content-[''] before:absolute before:top-[3px] before:left-[3px] before:size-[18px] before:rounded-full before:bg-white before:transition-transform before:duration-200 checked:before:translate-x-5 motion-reduce:before:transition-none">
<span class="order-1 flex flex-col gap-0.5 peer-disabled:opacity-80"><b class="text-[13.5px] font-semibold text-tm13-ink">Strictly necessary</b><small class="text-[11.5px] text-tm13-faint">Required for the site to function.</small></span>
</label>
<label class="flex items-center justify-between gap-4 py-2.5 cursor-pointer">
<input type="checkbox" id="tm-13-an" class="peer order-2 shrink-0 relative w-11 h-6 appearance-none rounded-full bg-tm13-off cursor-pointer transition-colors duration-200 checked:bg-tm13-acc before:content-[''] before:absolute before:top-[3px] before:left-[3px] before:size-[18px] before:rounded-full before:bg-tm13-knob before:transition-transform before:duration-200 checked:before:translate-x-5 checked:before:bg-white focus-visible:outline-2 focus-visible:outline-tm13-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:before:transition-none">
<span class="order-1 flex flex-col gap-0.5"><b class="text-[13.5px] font-semibold text-tm13-ink">Analytics</b><small class="text-[11.5px] text-tm13-faint">Helps us understand usage.</small></span>
</label>
<label class="flex items-center justify-between gap-4 py-2.5 cursor-pointer">
<input type="checkbox" id="tm-13-mk" class="peer order-2 shrink-0 relative w-11 h-6 appearance-none rounded-full bg-tm13-off cursor-pointer transition-colors duration-200 checked:bg-tm13-acc before:content-[''] before:absolute before:top-[3px] before:left-[3px] before:size-[18px] before:rounded-full before:bg-tm13-knob before:transition-transform before:duration-200 checked:before:translate-x-5 checked:before:bg-white focus-visible:outline-2 focus-visible:outline-tm13-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:before:transition-none">
<span class="order-1 flex flex-col gap-0.5"><b class="text-[13.5px] font-semibold text-tm13-ink">Marketing</b><small class="text-[11.5px] text-tm13-faint">Personalised offers & ads.</small></span>
</label>
</div>
<button type="button" data-tm13-save class="w-full min-h-11 mb-1 rounded-[10px] border border-tm13-edge bg-tm13-btn text-[13px] font-semibold text-tm13-ink cursor-pointer transition-colors duration-200 hover:bg-tm13-btnhover focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2">Save choices</button>
</details>
<div class="grid grid-cols-2 gap-2">
<button type="button" data-tm13-reject class="min-h-11 px-3 rounded-[10px] border border-tm13-edge bg-tm13-btn text-[13px] font-semibold text-tm13-ink cursor-pointer transition-colors duration-200 hover:bg-tm13-btnhover focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2">Reject all</button>
<button type="button" data-tm13-accept class="min-h-11 px-3 rounded-[10px] border-0 bg-tm13-acc text-[13px] font-bold text-tm13-onacc cursor-pointer transition-[filter] duration-200 hover:brightness-110 focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2">Accept all</button>
</div>
</div>
</section>
</section><section class="tm-13 relative w-full min-h-svh px-8 py-8 bg-tm13-bg font-tm13-sans">
<div class="min-h-[calc(100svh-64px)] grid place-items-center gap-4 content-center">
<p class="font-tm13-mono text-[13px] tracking-[0.1em] text-tm13-faint">Demo content behind the banner — the page stays usable.</p>
<button type="button" data-tm13-reopen hidden class="inline-flex items-center justify-center min-h-11 px-5 rounded-xl border border-tm13-edge bg-tm13-panel text-[13px] font-semibold text-tm13-ink cursor-pointer transition-colors duration-200 hover:border-tm13-acc focus-visible:outline-2 focus-visible:outline-tm13-acc focus-visible:outline-offset-2">Cookie settings</button>
</div>
<section data-tm13-banner role="region" aria-label="Cookie consent" class="fixed left-6 bottom-6 z-10 w-[min(420px,calc(100vw-48px))] rounded-2xl bg-tm13-card border border-tm13-cardedge shadow-[0_30px_70px_-24px_#000] translate-y-[140%] opacity-0 transition-[translate,opacity] duration-[400ms] ease-[cubic-bezier(0.4,0,0.2,1)] data-[in]:translate-y-0 data-[in]:opacity-100 motion-reduce:transition-none">
<div class="px-5 pt-5 pb-5">
<h2 class="mb-2 text-[17px] font-bold text-tm13-hi">We value your privacy</h2>
<p class="mb-4 text-[13px] leading-relaxed text-tm13-mut">We use cookies to run the site and, with your consent, to analyse traffic and personalise content. <a href="#privacy" class="text-tm13-acc no-underline hover:underline focus-visible:outline-2 focus-visible:outline-tm13-acc focus-visible:outline-offset-2">Cookie Policy</a>.</p>
<details data-tm13-prefs class="group mb-4 border-t border-tm13-edge">
<summary class="flex items-center justify-between gap-3 min-h-11 list-none cursor-pointer text-[13px] font-semibold text-tm13-ink focus-visible:outline-2 focus-visible:outline-tm13-acc focus-visible:outline-offset-2 rounded-md">
Preferences
<span aria-hidden="true" class="text-tm13-mut transition-transform duration-200 group-open:rotate-180 motion-reduce:transition-none">▾</span>
</summary>
<div class="flex flex-col gap-0.5 pb-1">
<label class="flex items-center justify-between gap-4 py-2.5 cursor-not-allowed">
<input type="checkbox" checked disabled aria-label="Strictly necessary (always on)" class="peer order-2 shrink-0 relative w-11 h-6 appearance-none rounded-full bg-tm13-acc opacity-70 cursor-not-allowed before:content-[''] before:absolute before:top-[3px] before:left-[3px] before:size-[18px] before:rounded-full before:bg-white before:transition-transform before:duration-200 checked:before:translate-x-5 motion-reduce:before:transition-none">
<span class="order-1 flex flex-col gap-0.5 peer-disabled:opacity-80"><b class="text-[13.5px] font-semibold text-tm13-ink">Strictly necessary</b><small class="text-[11.5px] text-tm13-faint">Required for the site to function.</small></span>
</label>
<label class="flex items-center justify-between gap-4 py-2.5 cursor-pointer">
<input type="checkbox" id="tm-13-an" class="peer order-2 shrink-0 relative w-11 h-6 appearance-none rounded-full bg-tm13-off cursor-pointer transition-colors duration-200 checked:bg-tm13-acc before:content-[''] before:absolute before:top-[3px] before:left-[3px] before:size-[18px] before:rounded-full before:bg-tm13-knob before:transition-transform before:duration-200 checked:before:translate-x-5 checked:before:bg-white focus-visible:outline-2 focus-visible:outline-tm13-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:before:transition-none">
<span class="order-1 flex flex-col gap-0.5"><b class="text-[13.5px] font-semibold text-tm13-ink">Analytics</b><small class="text-[11.5px] text-tm13-faint">Helps us understand usage.</small></span>
</label>
<label class="flex items-center justify-between gap-4 py-2.5 cursor-pointer">
<input type="checkbox" id="tm-13-mk" class="peer order-2 shrink-0 relative w-11 h-6 appearance-none rounded-full bg-tm13-off cursor-pointer transition-colors duration-200 checked:bg-tm13-acc before:content-[''] before:absolute before:top-[3px] before:left-[3px] before:size-[18px] before:rounded-full before:bg-tm13-knob before:transition-transform before:duration-200 checked:before:translate-x-5 checked:before:bg-white focus-visible:outline-2 focus-visible:outline-tm13-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:before:transition-none">
<span class="order-1 flex flex-col gap-0.5"><b class="text-[13.5px] font-semibold text-tm13-ink">Marketing</b><small class="text-[11.5px] text-tm13-faint">Personalised offers & ads.</small></span>
</label>
</div>
<button type="button" data-tm13-save class="w-full min-h-11 mb-1 rounded-[10px] border border-tm13-edge bg-tm13-btn text-[13px] font-semibold text-tm13-ink cursor-pointer transition-colors duration-200 hover:bg-tm13-btnhover focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2">Save choices</button>
</details>
<div class="grid grid-cols-2 gap-2">
<button type="button" data-tm13-reject class="min-h-11 px-3 rounded-[10px] border border-tm13-edge bg-tm13-btn text-[13px] font-semibold text-tm13-ink cursor-pointer transition-colors duration-200 hover:bg-tm13-btnhover focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2">Reject all</button>
<button type="button" data-tm13-accept class="min-h-11 px-3 rounded-[10px] border-0 bg-tm13-acc text-[13px] font-bold text-tm13-onacc cursor-pointer transition-[filter] duration-200 hover:brightness-110 focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2">Accept all</button>
</div>
</div>
</section>
</section>@import "tailwindcss";
@theme {
--color-tm13-bg: #07080b;
--color-tm13-bg: oklch(0.15 0.012 260);
--color-tm13-card: #151a24;
--color-tm13-card: oklch(0.21 0.017 260);
--color-tm13-panel: #141822;
--color-tm13-panel: oklch(0.2 0.016 260);
--color-tm13-btn: #1a2030;
--color-tm13-btn: oklch(0.23 0.02 260);
--color-tm13-btnhover: #232b3d;
--color-tm13-btnhover: oklch(0.27 0.025 260);
--color-tm13-edge: #2e3849;
--color-tm13-edge: oklch(0.32 0.02 260);
--color-tm13-cardedge: #273041;
--color-tm13-cardedge: oklch(0.29 0.02 260);
--color-tm13-off: #2a3342;
--color-tm13-off: oklch(0.3 0.02 260);
--color-tm13-knob: #9aa3b4;
--color-tm13-knob: oklch(0.71 0.018 260);
--color-tm13-hi: #f1f4fa;
--color-tm13-hi: oklch(0.96 0.006 260);
--color-tm13-ink: #dbe1ec;
--color-tm13-ink: oklch(0.9 0.012 260);
--color-tm13-mut: #9aa3b4;
--color-tm13-mut: oklch(0.71 0.018 260);
--color-tm13-faint: #81899c;
--color-tm13-faint: oklch(0.64 0.02 260);
--color-tm13-acc: #7fb0ff;
--color-tm13-acc: oklch(0.75 0.14 250);
--color-tm13-onacc: #06101f;
--color-tm13-onacc: oklch(0.18 0.04 250);
--font-tm13-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tm13-mono: 'JetBrains Mono', ui-monospace, monospace;
}
.tm-13 {
width: 100%;
}
/* Safari still paints a marker on a list-none <summary>. */
.tm-13 summary::-webkit-details-marker {
display: none;
}@import "tailwindcss";
@theme {
--color-tm13-bg: #07080b;
--color-tm13-bg: oklch(0.15 0.012 260);
--color-tm13-card: #151a24;
--color-tm13-card: oklch(0.21 0.017 260);
--color-tm13-panel: #141822;
--color-tm13-panel: oklch(0.2 0.016 260);
--color-tm13-btn: #1a2030;
--color-tm13-btn: oklch(0.23 0.02 260);
--color-tm13-btnhover: #232b3d;
--color-tm13-btnhover: oklch(0.27 0.025 260);
--color-tm13-edge: #2e3849;
--color-tm13-edge: oklch(0.32 0.02 260);
--color-tm13-cardedge: #273041;
--color-tm13-cardedge: oklch(0.29 0.02 260);
--color-tm13-off: #2a3342;
--color-tm13-off: oklch(0.3 0.02 260);
--color-tm13-knob: #9aa3b4;
--color-tm13-knob: oklch(0.71 0.018 260);
--color-tm13-hi: #f1f4fa;
--color-tm13-hi: oklch(0.96 0.006 260);
--color-tm13-ink: #dbe1ec;
--color-tm13-ink: oklch(0.9 0.012 260);
--color-tm13-mut: #9aa3b4;
--color-tm13-mut: oklch(0.71 0.018 260);
--color-tm13-faint: #81899c;
--color-tm13-faint: oklch(0.64 0.02 260);
--color-tm13-acc: #7fb0ff;
--color-tm13-acc: oklch(0.75 0.14 250);
--color-tm13-onacc: #06101f;
--color-tm13-onacc: oklch(0.18 0.04 250);
--font-tm13-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tm13-mono: 'JetBrains Mono', ui-monospace, monospace;
}
.tm-13 {
width: 100%;
}
/* Safari still paints a marker on a list-none <summary>. */
.tm-13 summary::-webkit-details-marker {
display: none;
}(function () {
var root = document.querySelector('.tm-13');
var banner = root.querySelector('[data-tm13-banner]');
var reopen = root.querySelector('[data-tm13-reopen]');
var prefs = root.querySelector('[data-tm13-prefs]');
var KEY = 'tm-13-consent';
function store(v) { try { localStorage.setItem(KEY, JSON.stringify(Object.assign({ at: Date.now() }, v))); } catch (e) {} }
function read() { try { return JSON.parse(localStorage.getItem(KEY)); } catch (e) { return null; } }
function hide() {
delete banner.dataset.in;
setTimeout(function () { banner.style.display = 'none'; }, 400);
reopen.hidden = false;
}
function open() {
banner.style.display = ''; reopen.hidden = true; prefs.open = false;
requestAnimationFrame(function () { banner.dataset.in = ''; });
}
root.querySelector('[data-tm13-accept]').addEventListener('click', function () {
store({ necessary: true, analytics: true, marketing: true }); hide();
});
root.querySelector('[data-tm13-reject]').addEventListener('click', function () {
store({ necessary: true, analytics: false, marketing: false }); hide();
});
root.querySelector('[data-tm13-save]').addEventListener('click', function () {
store({
necessary: true,
analytics: root.querySelector('#tm-13-an').checked,
marketing: root.querySelector('#tm-13-mk').checked
});
hide();
});
reopen.addEventListener('click', open);
if (read()) { banner.style.display = 'none'; reopen.hidden = false; }
else requestAnimationFrame(function () { banner.dataset.in = ''; });
})();(function () {
var root = document.querySelector('.tm-13');
var banner = root.querySelector('[data-tm13-banner]');
var reopen = root.querySelector('[data-tm13-reopen]');
var prefs = root.querySelector('[data-tm13-prefs]');
var KEY = 'tm-13-consent';
function store(v) { try { localStorage.setItem(KEY, JSON.stringify(Object.assign({ at: Date.now() }, v))); } catch (e) {} }
function read() { try { return JSON.parse(localStorage.getItem(KEY)); } catch (e) { return null; } }
function hide() {
delete banner.dataset.in;
setTimeout(function () { banner.style.display = 'none'; }, 400);
reopen.hidden = false;
}
function open() {
banner.style.display = ''; reopen.hidden = true; prefs.open = false;
requestAnimationFrame(function () { banner.dataset.in = ''; });
}
root.querySelector('[data-tm13-accept]').addEventListener('click', function () {
store({ necessary: true, analytics: true, marketing: true }); hide();
});
root.querySelector('[data-tm13-reject]').addEventListener('click', function () {
store({ necessary: true, analytics: false, marketing: false }); hide();
});
root.querySelector('[data-tm13-save]').addEventListener('click', function () {
store({
necessary: true,
analytics: root.querySelector('#tm-13-an').checked,
marketing: root.querySelector('#tm-13-mk').checked
});
hide();
});
reopen.addEventListener('click', open);
if (read()) { banner.style.display = 'none'; reopen.hidden = false; }
else requestAnimationFrame(function () { banner.dataset.in = ''; });
})();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-tm13-bg: #07080b;
--color-tm13-bg: oklch(0.15 0.012 260);
--color-tm13-card: #151a24;
--color-tm13-card: oklch(0.21 0.017 260);
--color-tm13-panel: #141822;
--color-tm13-panel: oklch(0.2 0.016 260);
--color-tm13-btn: #1a2030;
--color-tm13-btn: oklch(0.23 0.02 260);
--color-tm13-btnhover: #232b3d;
--color-tm13-btnhover: oklch(0.27 0.025 260);
--color-tm13-edge: #2e3849;
--color-tm13-edge: oklch(0.32 0.02 260);
--color-tm13-cardedge: #273041;
--color-tm13-cardedge: oklch(0.29 0.02 260);
--color-tm13-off: #2a3342;
--color-tm13-off: oklch(0.3 0.02 260);
--color-tm13-knob: #9aa3b4;
--color-tm13-knob: oklch(0.71 0.018 260);
--color-tm13-hi: #f1f4fa;
--color-tm13-hi: oklch(0.96 0.006 260);
--color-tm13-ink: #dbe1ec;
--color-tm13-ink: oklch(0.9 0.012 260);
--color-tm13-mut: #9aa3b4;
--color-tm13-mut: oklch(0.71 0.018 260);
--color-tm13-faint: #81899c;
--color-tm13-faint: oklch(0.64 0.02 260);
--color-tm13-acc: #7fb0ff;
--color-tm13-acc: oklch(0.75 0.14 250);
--color-tm13-onacc: #06101f;
--color-tm13-onacc: oklch(0.18 0.04 250);
--font-tm13-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tm13-mono: 'JetBrains Mono', ui-monospace, monospace;
}
.tm-13 {
width: 100%;
}
/* Safari still paints a marker on a list-none <summary>. */
.tm-13 summary::-webkit-details-marker {
display: none;
}@import "tailwindcss";
@theme {
--color-tm13-bg: #07080b;
--color-tm13-bg: oklch(0.15 0.012 260);
--color-tm13-card: #151a24;
--color-tm13-card: oklch(0.21 0.017 260);
--color-tm13-panel: #141822;
--color-tm13-panel: oklch(0.2 0.016 260);
--color-tm13-btn: #1a2030;
--color-tm13-btn: oklch(0.23 0.02 260);
--color-tm13-btnhover: #232b3d;
--color-tm13-btnhover: oklch(0.27 0.025 260);
--color-tm13-edge: #2e3849;
--color-tm13-edge: oklch(0.32 0.02 260);
--color-tm13-cardedge: #273041;
--color-tm13-cardedge: oklch(0.29 0.02 260);
--color-tm13-off: #2a3342;
--color-tm13-off: oklch(0.3 0.02 260);
--color-tm13-knob: #9aa3b4;
--color-tm13-knob: oklch(0.71 0.018 260);
--color-tm13-hi: #f1f4fa;
--color-tm13-hi: oklch(0.96 0.006 260);
--color-tm13-ink: #dbe1ec;
--color-tm13-ink: oklch(0.9 0.012 260);
--color-tm13-mut: #9aa3b4;
--color-tm13-mut: oklch(0.71 0.018 260);
--color-tm13-faint: #81899c;
--color-tm13-faint: oklch(0.64 0.02 260);
--color-tm13-acc: #7fb0ff;
--color-tm13-acc: oklch(0.75 0.14 250);
--color-tm13-onacc: #06101f;
--color-tm13-onacc: oklch(0.18 0.04 250);
--font-tm13-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tm13-mono: 'JetBrains Mono', ui-monospace, monospace;
}
.tm-13 {
width: 100%;
}
/* Safari still paints a marker on a list-none <summary>. */
.tm-13 summary::-webkit-details-marker {
display: none;
}<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=Space+Grotesk:wght@400;500;600;700&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=Space+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- Two pieces of imperative state disappear: the
hiddentoggling of the preferences panel and the save button (now<details>plusgroup-open:), and theis-inclass (now adata-inattribute read bydata-[in]:). The controller is left doing only what it should: persisting a decision. - The toggle switch is
appearance-noneplus abefore:knob — about 12 utilities per switch, versus 9 CSS declarations shared by all three. This is the clearest case in the collection where the CSS version is objectively tidier, and the trade is that each switch's behaviour is visible where it is used. - UI change from the CSS original: the action row is a 2-column grid so Reject and Accept are provably equal width (GDPR asks for equal prominence, and the original's flex row made Accept wider at some widths), every button is 44px, and Preferences is a real disclosure rather than a button that toggles two hidden attributes.
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 Consent Banner with details Disclosure
Source: https://codefronts.com/components/tailwind-css-modals/tailwind-gdpr-cookie-consent-banner-details-disclosure/
A corner consent sheet with equal-weight Accept and Reject, a <details> preferences panel and real toggle switches. The port deletes state-tracking JavaScript twice over: the disclosure becomes a <details> read by open:, and the slide-in becomes data-in read by data-[in]:.
## HTML
```html
<section class="tm-13 relative w-full min-h-svh px-8 py-8 bg-tm13-bg font-tm13-sans">
<div class="min-h-[calc(100svh-64px)] grid place-items-center gap-4 content-center">
<p class="font-tm13-mono text-[13px] tracking-[0.1em] text-tm13-faint">Demo content behind the banner — the page stays usable.</p>
<button type="button" data-tm13-reopen hidden class="inline-flex items-center justify-center min-h-11 px-5 rounded-xl border border-tm13-edge bg-tm13-panel text-[13px] font-semibold text-tm13-ink cursor-pointer transition-colors duration-200 hover:border-tm13-acc focus-visible:outline-2 focus-visible:outline-tm13-acc focus-visible:outline-offset-2">Cookie settings</button>
</div>
<section data-tm13-banner role="region" aria-label="Cookie consent" class="fixed left-6 bottom-6 z-10 w-[min(420px,calc(100vw-48px))] rounded-2xl bg-tm13-card border border-tm13-cardedge shadow-[0_30px_70px_-24px_#000] translate-y-[140%] opacity-0 transition-[translate,opacity] duration-[400ms] ease-[cubic-bezier(0.4,0,0.2,1)] data-[in]:translate-y-0 data-[in]:opacity-100 motion-reduce:transition-none">
<div class="px-5 pt-5 pb-5">
<h2 class="mb-2 text-[17px] font-bold text-tm13-hi">We value your privacy</h2>
<p class="mb-4 text-[13px] leading-relaxed text-tm13-mut">We use cookies to run the site and, with your consent, to analyse traffic and personalise content. <a href="#privacy" class="text-tm13-acc no-underline hover:underline focus-visible:outline-2 focus-visible:outline-tm13-acc focus-visible:outline-offset-2">Cookie Policy</a>.</p>
<details data-tm13-prefs class="group mb-4 border-t border-tm13-edge">
<summary class="flex items-center justify-between gap-3 min-h-11 list-none cursor-pointer text-[13px] font-semibold text-tm13-ink focus-visible:outline-2 focus-visible:outline-tm13-acc focus-visible:outline-offset-2 rounded-md">
Preferences
<span aria-hidden="true" class="text-tm13-mut transition-transform duration-200 group-open:rotate-180 motion-reduce:transition-none">▾</span>
</summary>
<div class="flex flex-col gap-0.5 pb-1">
<label class="flex items-center justify-between gap-4 py-2.5 cursor-not-allowed">
<input type="checkbox" checked disabled aria-label="Strictly necessary (always on)" class="peer order-2 shrink-0 relative w-11 h-6 appearance-none rounded-full bg-tm13-acc opacity-70 cursor-not-allowed before:content-[''] before:absolute before:top-[3px] before:left-[3px] before:size-[18px] before:rounded-full before:bg-white before:transition-transform before:duration-200 checked:before:translate-x-5 motion-reduce:before:transition-none">
<span class="order-1 flex flex-col gap-0.5 peer-disabled:opacity-80"><b class="text-[13.5px] font-semibold text-tm13-ink">Strictly necessary</b><small class="text-[11.5px] text-tm13-faint">Required for the site to function.</small></span>
</label>
<label class="flex items-center justify-between gap-4 py-2.5 cursor-pointer">
<input type="checkbox" id="tm-13-an" class="peer order-2 shrink-0 relative w-11 h-6 appearance-none rounded-full bg-tm13-off cursor-pointer transition-colors duration-200 checked:bg-tm13-acc before:content-[''] before:absolute before:top-[3px] before:left-[3px] before:size-[18px] before:rounded-full before:bg-tm13-knob before:transition-transform before:duration-200 checked:before:translate-x-5 checked:before:bg-white focus-visible:outline-2 focus-visible:outline-tm13-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:before:transition-none">
<span class="order-1 flex flex-col gap-0.5"><b class="text-[13.5px] font-semibold text-tm13-ink">Analytics</b><small class="text-[11.5px] text-tm13-faint">Helps us understand usage.</small></span>
</label>
<label class="flex items-center justify-between gap-4 py-2.5 cursor-pointer">
<input type="checkbox" id="tm-13-mk" class="peer order-2 shrink-0 relative w-11 h-6 appearance-none rounded-full bg-tm13-off cursor-pointer transition-colors duration-200 checked:bg-tm13-acc before:content-[''] before:absolute before:top-[3px] before:left-[3px] before:size-[18px] before:rounded-full before:bg-tm13-knob before:transition-transform before:duration-200 checked:before:translate-x-5 checked:before:bg-white focus-visible:outline-2 focus-visible:outline-tm13-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:before:transition-none">
<span class="order-1 flex flex-col gap-0.5"><b class="text-[13.5px] font-semibold text-tm13-ink">Marketing</b><small class="text-[11.5px] text-tm13-faint">Personalised offers & ads.</small></span>
</label>
</div>
<button type="button" data-tm13-save class="w-full min-h-11 mb-1 rounded-[10px] border border-tm13-edge bg-tm13-btn text-[13px] font-semibold text-tm13-ink cursor-pointer transition-colors duration-200 hover:bg-tm13-btnhover focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2">Save choices</button>
</details>
<div class="grid grid-cols-2 gap-2">
<button type="button" data-tm13-reject class="min-h-11 px-3 rounded-[10px] border border-tm13-edge bg-tm13-btn text-[13px] font-semibold text-tm13-ink cursor-pointer transition-colors duration-200 hover:bg-tm13-btnhover focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2">Reject all</button>
<button type="button" data-tm13-accept class="min-h-11 px-3 rounded-[10px] border-0 bg-tm13-acc text-[13px] font-bold text-tm13-onacc cursor-pointer transition-[filter] duration-200 hover:brightness-110 focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2">Accept all</button>
</div>
</div>
</section>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tm13-bg: #07080b;
--color-tm13-bg: oklch(0.15 0.012 260);
--color-tm13-card: #151a24;
--color-tm13-card: oklch(0.21 0.017 260);
--color-tm13-panel: #141822;
--color-tm13-panel: oklch(0.2 0.016 260);
--color-tm13-btn: #1a2030;
--color-tm13-btn: oklch(0.23 0.02 260);
--color-tm13-btnhover: #232b3d;
--color-tm13-btnhover: oklch(0.27 0.025 260);
--color-tm13-edge: #2e3849;
--color-tm13-edge: oklch(0.32 0.02 260);
--color-tm13-cardedge: #273041;
--color-tm13-cardedge: oklch(0.29 0.02 260);
--color-tm13-off: #2a3342;
--color-tm13-off: oklch(0.3 0.02 260);
--color-tm13-knob: #9aa3b4;
--color-tm13-knob: oklch(0.71 0.018 260);
--color-tm13-hi: #f1f4fa;
--color-tm13-hi: oklch(0.96 0.006 260);
--color-tm13-ink: #dbe1ec;
--color-tm13-ink: oklch(0.9 0.012 260);
--color-tm13-mut: #9aa3b4;
--color-tm13-mut: oklch(0.71 0.018 260);
--color-tm13-faint: #81899c;
--color-tm13-faint: oklch(0.64 0.02 260);
--color-tm13-acc: #7fb0ff;
--color-tm13-acc: oklch(0.75 0.14 250);
--color-tm13-onacc: #06101f;
--color-tm13-onacc: oklch(0.18 0.04 250);
--font-tm13-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tm13-mono: 'JetBrains Mono', ui-monospace, monospace;
}
.tm-13 {
width: 100%;
}
/* Safari still paints a marker on a list-none <summary>. */
.tm-13 summary::-webkit-details-marker {
display: none;
}
```
## JavaScript
```js
(function () {
var root = document.querySelector('.tm-13');
var banner = root.querySelector('[data-tm13-banner]');
var reopen = root.querySelector('[data-tm13-reopen]');
var prefs = root.querySelector('[data-tm13-prefs]');
var KEY = 'tm-13-consent';
function store(v) { try { localStorage.setItem(KEY, JSON.stringify(Object.assign({ at: Date.now() }, v))); } catch (e) {} }
function read() { try { return JSON.parse(localStorage.getItem(KEY)); } catch (e) { return null; } }
function hide() {
delete banner.dataset.in;
setTimeout(function () { banner.style.display = 'none'; }, 400);
reopen.hidden = false;
}
function open() {
banner.style.display = ''; reopen.hidden = true; prefs.open = false;
requestAnimationFrame(function () { banner.dataset.in = ''; });
}
root.querySelector('[data-tm13-accept]').addEventListener('click', function () {
store({ necessary: true, analytics: true, marketing: true }); hide();
});
root.querySelector('[data-tm13-reject]').addEventListener('click', function () {
store({ necessary: true, analytics: false, marketing: false }); hide();
});
root.querySelector('[data-tm13-save]').addEventListener('click', function () {
store({
necessary: true,
analytics: root.querySelector('#tm-13-an').checked,
marketing: root.querySelector('#tm-13-mk').checked
});
hide();
});
reopen.addEventListener('click', open);
if (read()) { banner.style.display = 'none'; reopen.hidden = false; }
else requestAnimationFrame(function () { banner.dataset.in = ''; });
})();
```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 Consent Banner with details Disclosure
Source: https://codefronts.com/components/tailwind-css-modals/tailwind-gdpr-cookie-consent-banner-details-disclosure/
A corner consent sheet with equal-weight Accept and Reject, a <details> preferences panel and real toggle switches. The port deletes state-tracking JavaScript twice over: the disclosure becomes a <details> read by open:, and the slide-in becomes data-in read by data-[in]:.
## HTML
```html
<section class="tm-13 relative w-full min-h-svh px-8 py-8 bg-tm13-bg font-tm13-sans">
<div class="min-h-[calc(100svh-64px)] grid place-items-center gap-4 content-center">
<p class="font-tm13-mono text-[13px] tracking-[0.1em] text-tm13-faint">Demo content behind the banner — the page stays usable.</p>
<button type="button" data-tm13-reopen hidden class="inline-flex items-center justify-center min-h-11 px-5 rounded-xl border border-tm13-edge bg-tm13-panel text-[13px] font-semibold text-tm13-ink cursor-pointer transition-colors duration-200 hover:border-tm13-acc focus-visible:outline-2 focus-visible:outline-tm13-acc focus-visible:outline-offset-2">Cookie settings</button>
</div>
<section data-tm13-banner role="region" aria-label="Cookie consent" class="fixed left-6 bottom-6 z-10 w-[min(420px,calc(100vw-48px))] rounded-2xl bg-tm13-card border border-tm13-cardedge shadow-[0_30px_70px_-24px_#000] translate-y-[140%] opacity-0 transition-[translate,opacity] duration-[400ms] ease-[cubic-bezier(0.4,0,0.2,1)] data-[in]:translate-y-0 data-[in]:opacity-100 motion-reduce:transition-none">
<div class="px-5 pt-5 pb-5">
<h2 class="mb-2 text-[17px] font-bold text-tm13-hi">We value your privacy</h2>
<p class="mb-4 text-[13px] leading-relaxed text-tm13-mut">We use cookies to run the site and, with your consent, to analyse traffic and personalise content. <a href="#privacy" class="text-tm13-acc no-underline hover:underline focus-visible:outline-2 focus-visible:outline-tm13-acc focus-visible:outline-offset-2">Cookie Policy</a>.</p>
<details data-tm13-prefs class="group mb-4 border-t border-tm13-edge">
<summary class="flex items-center justify-between gap-3 min-h-11 list-none cursor-pointer text-[13px] font-semibold text-tm13-ink focus-visible:outline-2 focus-visible:outline-tm13-acc focus-visible:outline-offset-2 rounded-md">
Preferences
<span aria-hidden="true" class="text-tm13-mut transition-transform duration-200 group-open:rotate-180 motion-reduce:transition-none">▾</span>
</summary>
<div class="flex flex-col gap-0.5 pb-1">
<label class="flex items-center justify-between gap-4 py-2.5 cursor-not-allowed">
<input type="checkbox" checked disabled aria-label="Strictly necessary (always on)" class="peer order-2 shrink-0 relative w-11 h-6 appearance-none rounded-full bg-tm13-acc opacity-70 cursor-not-allowed before:content-[''] before:absolute before:top-[3px] before:left-[3px] before:size-[18px] before:rounded-full before:bg-white before:transition-transform before:duration-200 checked:before:translate-x-5 motion-reduce:before:transition-none">
<span class="order-1 flex flex-col gap-0.5 peer-disabled:opacity-80"><b class="text-[13.5px] font-semibold text-tm13-ink">Strictly necessary</b><small class="text-[11.5px] text-tm13-faint">Required for the site to function.</small></span>
</label>
<label class="flex items-center justify-between gap-4 py-2.5 cursor-pointer">
<input type="checkbox" id="tm-13-an" class="peer order-2 shrink-0 relative w-11 h-6 appearance-none rounded-full bg-tm13-off cursor-pointer transition-colors duration-200 checked:bg-tm13-acc before:content-[''] before:absolute before:top-[3px] before:left-[3px] before:size-[18px] before:rounded-full before:bg-tm13-knob before:transition-transform before:duration-200 checked:before:translate-x-5 checked:before:bg-white focus-visible:outline-2 focus-visible:outline-tm13-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:before:transition-none">
<span class="order-1 flex flex-col gap-0.5"><b class="text-[13.5px] font-semibold text-tm13-ink">Analytics</b><small class="text-[11.5px] text-tm13-faint">Helps us understand usage.</small></span>
</label>
<label class="flex items-center justify-between gap-4 py-2.5 cursor-pointer">
<input type="checkbox" id="tm-13-mk" class="peer order-2 shrink-0 relative w-11 h-6 appearance-none rounded-full bg-tm13-off cursor-pointer transition-colors duration-200 checked:bg-tm13-acc before:content-[''] before:absolute before:top-[3px] before:left-[3px] before:size-[18px] before:rounded-full before:bg-tm13-knob before:transition-transform before:duration-200 checked:before:translate-x-5 checked:before:bg-white focus-visible:outline-2 focus-visible:outline-tm13-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:before:transition-none">
<span class="order-1 flex flex-col gap-0.5"><b class="text-[13.5px] font-semibold text-tm13-ink">Marketing</b><small class="text-[11.5px] text-tm13-faint">Personalised offers & ads.</small></span>
</label>
</div>
<button type="button" data-tm13-save class="w-full min-h-11 mb-1 rounded-[10px] border border-tm13-edge bg-tm13-btn text-[13px] font-semibold text-tm13-ink cursor-pointer transition-colors duration-200 hover:bg-tm13-btnhover focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2">Save choices</button>
</details>
<div class="grid grid-cols-2 gap-2">
<button type="button" data-tm13-reject class="min-h-11 px-3 rounded-[10px] border border-tm13-edge bg-tm13-btn text-[13px] font-semibold text-tm13-ink cursor-pointer transition-colors duration-200 hover:bg-tm13-btnhover focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2">Reject all</button>
<button type="button" data-tm13-accept class="min-h-11 px-3 rounded-[10px] border-0 bg-tm13-acc text-[13px] font-bold text-tm13-onacc cursor-pointer transition-[filter] duration-200 hover:brightness-110 focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2">Accept all</button>
</div>
</div>
</section>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tm13-bg: #07080b;
--color-tm13-bg: oklch(0.15 0.012 260);
--color-tm13-card: #151a24;
--color-tm13-card: oklch(0.21 0.017 260);
--color-tm13-panel: #141822;
--color-tm13-panel: oklch(0.2 0.016 260);
--color-tm13-btn: #1a2030;
--color-tm13-btn: oklch(0.23 0.02 260);
--color-tm13-btnhover: #232b3d;
--color-tm13-btnhover: oklch(0.27 0.025 260);
--color-tm13-edge: #2e3849;
--color-tm13-edge: oklch(0.32 0.02 260);
--color-tm13-cardedge: #273041;
--color-tm13-cardedge: oklch(0.29 0.02 260);
--color-tm13-off: #2a3342;
--color-tm13-off: oklch(0.3 0.02 260);
--color-tm13-knob: #9aa3b4;
--color-tm13-knob: oklch(0.71 0.018 260);
--color-tm13-hi: #f1f4fa;
--color-tm13-hi: oklch(0.96 0.006 260);
--color-tm13-ink: #dbe1ec;
--color-tm13-ink: oklch(0.9 0.012 260);
--color-tm13-mut: #9aa3b4;
--color-tm13-mut: oklch(0.71 0.018 260);
--color-tm13-faint: #81899c;
--color-tm13-faint: oklch(0.64 0.02 260);
--color-tm13-acc: #7fb0ff;
--color-tm13-acc: oklch(0.75 0.14 250);
--color-tm13-onacc: #06101f;
--color-tm13-onacc: oklch(0.18 0.04 250);
--font-tm13-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tm13-mono: 'JetBrains Mono', ui-monospace, monospace;
}
.tm-13 {
width: 100%;
}
/* Safari still paints a marker on a list-none <summary>. */
.tm-13 summary::-webkit-details-marker {
display: none;
}
```
## JavaScript
```js
(function () {
var root = document.querySelector('.tm-13');
var banner = root.querySelector('[data-tm13-banner]');
var reopen = root.querySelector('[data-tm13-reopen]');
var prefs = root.querySelector('[data-tm13-prefs]');
var KEY = 'tm-13-consent';
function store(v) { try { localStorage.setItem(KEY, JSON.stringify(Object.assign({ at: Date.now() }, v))); } catch (e) {} }
function read() { try { return JSON.parse(localStorage.getItem(KEY)); } catch (e) { return null; } }
function hide() {
delete banner.dataset.in;
setTimeout(function () { banner.style.display = 'none'; }, 400);
reopen.hidden = false;
}
function open() {
banner.style.display = ''; reopen.hidden = true; prefs.open = false;
requestAnimationFrame(function () { banner.dataset.in = ''; });
}
root.querySelector('[data-tm13-accept]').addEventListener('click', function () {
store({ necessary: true, analytics: true, marketing: true }); hide();
});
root.querySelector('[data-tm13-reject]').addEventListener('click', function () {
store({ necessary: true, analytics: false, marketing: false }); hide();
});
root.querySelector('[data-tm13-save]').addEventListener('click', function () {
store({
necessary: true,
analytics: root.querySelector('#tm-13-an').checked,
marketing: root.querySelector('#tm-13-mk').checked
});
hide();
});
reopen.addEventListener('click', open);
if (read()) { banner.style.display = 'none'; reopen.hidden = false; }
else requestAnimationFrame(function () { banner.dataset.in = ''; });
})();
```How this works
Not a dialog, deliberately. Consent must not hold the page hostage, so this is a role="region" banner pinned to a corner: the page stays scrollable and operable, and Reject is exactly one click, the same as Accept. A modal consent wall is both a worse experience and, in several jurisdictions, a worse legal position.
The disclosure is native. <details> gives keyboard operation, correct semantics and an open state for free; open:rotate-180 turns the chevron and open:block reveals the save button. The original needed two hidden-attribute toggles in script for the same effect.
The switch is a checkbox. appearance-none strips the native rendering; a before: knob translates on checked:. It remains a real checkbox: focusable, announced as a checkbox, operable with Space.
Make it yours
- Add a category by copying one row; the layout is
flex items-center justify-between, so nothing else changes. - Load each vendor's script only inside the branch that accepted its category.
- Move to a full-width bottom bar with
inset-x-0 bottom-0 w-full rounded-none.
Gotchas — read before shipping
- Reject must be as prominent and as immediate as Accept, and no non-essential box may start ticked. The demo's equal-weight action row is the compliance-relevant part of the design, not a style choice.
- Fire analytics and marketing scripts after consent, never before with a retroactive opt-out.
- Store a timestamp alongside the choice so you can honour re-consent windows, and wrap
localStoragein try/catch — it throws in sandboxed frames and in some privacy modes. - A
<summary>withlist-noneloses its marker in every browser except Safari, which needs[&::-webkit-details-marker]:hidden— one of the few shims that must stay in CSS.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
<details>, appearance-none and localStorage are universal; the quoted floor is Tailwind v4's generated sheet.