03 / 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 Age Verification Gate with :has() and Zero JavaScript
A full-screen entry gate that blurs and blocks the page until the visitor confirms their age — still zero JavaScript, now keyboard-operable. The port reads one checkbox from two directions: has-checked: on the wrapper un-blurs the article, and peer-checked: on the gate fades it out.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tm-03 group relative w-full min-h-svh overflow-hidden bg-tm03-bg font-tm03-sans">
<article class="min-h-svh flex flex-col items-center justify-center gap-5 p-10 text-center blur-[9px] scale-[1.02] transition-[filter,transform] duration-500 group-has-checked:blur-none group-has-checked:scale-100 motion-reduce:transition-none">
<h1 class="text-[clamp(38px,9vw,74px)] font-extrabold tracking-[0.12em] text-tm03-hi">CASK & OAK</h1>
<p class="max-w-[44ch] text-base leading-relaxed text-tm03-mut">Small-batch single malt, aged in highland oak. Explore the reserve collection.</p>
<div aria-hidden="true" class="flex gap-4 mt-2">
<span class="w-[90px] h-[120px] rounded-xl border border-tm03-edge bg-tm03-tile"></span>
<span class="w-[90px] h-[120px] rounded-xl border border-tm03-edge bg-tm03-tile"></span>
<span class="w-[90px] h-[120px] rounded-xl border border-tm03-edge bg-tm03-tile"></span>
</div>
</article>
<input type="checkbox" id="tm-03-ok" class="peer sr-only">
<div role="dialog" aria-modal="true" aria-labelledby="tm-03-t" aria-describedby="tm-03-d" class="fixed inset-0 z-10 grid place-items-center p-6 bg-tm03-scrim backdrop-blur-[3px] transition-[opacity,visibility] duration-[450ms] peer-checked:opacity-0 peer-checked:invisible motion-reduce:transition-none">
<div class="w-[min(430px,94vw)] px-8 py-10 text-center rounded-[20px] bg-tm03-panel border border-tm03-edge shadow-[0_40px_90px_-30px_#000]">
<div aria-hidden="true" class="grid place-items-center size-20 mx-auto mb-5 rounded-full border-2 border-tm03-accedge text-[26px] font-extrabold text-tm03-acc shadow-[0_0_40px_-10px_var(--color-tm03-acc),inset_0_0_24px_-12px_var(--color-tm03-acc)]">21+</div>
<h2 id="tm-03-t" class="mb-2.5 text-[22px] font-bold text-tm03-hi">Are you of legal drinking age?</h2>
<p id="tm-03-d" class="mb-6 text-sm text-tm03-mut">You must be 21 or older to enter this site.</p>
<div class="flex flex-wrap justify-center gap-3">
<label for="tm-03-ok" class="inline-flex items-center justify-center min-h-11 px-6 rounded-xl text-sm font-semibold cursor-pointer bg-tm03-acc text-tm03-onacc border border-tm03-acc transition-[filter] duration-200 hover:brightness-110 group-has-[#tm-03-ok:focus-visible]:outline-2 group-has-[#tm-03-ok:focus-visible]:outline-tm03-acc group-has-[#tm-03-ok:focus-visible]:outline-offset-[3px]">Yes, I am 21+</label>
<a href="https://responsibility.org" class="inline-flex items-center justify-center min-h-11 px-6 rounded-xl text-sm font-semibold no-underline text-tm03-mut border border-tm03-edge transition-colors duration-200 hover:text-tm03-ink hover:border-tm03-accedge focus-visible:outline-2 focus-visible:outline-tm03-acc focus-visible:outline-offset-[3px]">No, exit</a>
</div>
<p class="mt-5 text-xs tracking-[0.06em] text-tm03-faint">Press <kbd class="px-1.5 py-0.5 rounded border border-tm03-edge text-[11px]">Space</kbd> to confirm · please enjoy responsibly.</p>
</div>
</div>
</section><section class="tm-03 group relative w-full min-h-svh overflow-hidden bg-tm03-bg font-tm03-sans">
<article class="min-h-svh flex flex-col items-center justify-center gap-5 p-10 text-center blur-[9px] scale-[1.02] transition-[filter,transform] duration-500 group-has-checked:blur-none group-has-checked:scale-100 motion-reduce:transition-none">
<h1 class="text-[clamp(38px,9vw,74px)] font-extrabold tracking-[0.12em] text-tm03-hi">CASK & OAK</h1>
<p class="max-w-[44ch] text-base leading-relaxed text-tm03-mut">Small-batch single malt, aged in highland oak. Explore the reserve collection.</p>
<div aria-hidden="true" class="flex gap-4 mt-2">
<span class="w-[90px] h-[120px] rounded-xl border border-tm03-edge bg-tm03-tile"></span>
<span class="w-[90px] h-[120px] rounded-xl border border-tm03-edge bg-tm03-tile"></span>
<span class="w-[90px] h-[120px] rounded-xl border border-tm03-edge bg-tm03-tile"></span>
</div>
</article>
<input type="checkbox" id="tm-03-ok" class="peer sr-only">
<div role="dialog" aria-modal="true" aria-labelledby="tm-03-t" aria-describedby="tm-03-d" class="fixed inset-0 z-10 grid place-items-center p-6 bg-tm03-scrim backdrop-blur-[3px] transition-[opacity,visibility] duration-[450ms] peer-checked:opacity-0 peer-checked:invisible motion-reduce:transition-none">
<div class="w-[min(430px,94vw)] px-8 py-10 text-center rounded-[20px] bg-tm03-panel border border-tm03-edge shadow-[0_40px_90px_-30px_#000]">
<div aria-hidden="true" class="grid place-items-center size-20 mx-auto mb-5 rounded-full border-2 border-tm03-accedge text-[26px] font-extrabold text-tm03-acc shadow-[0_0_40px_-10px_var(--color-tm03-acc),inset_0_0_24px_-12px_var(--color-tm03-acc)]">21+</div>
<h2 id="tm-03-t" class="mb-2.5 text-[22px] font-bold text-tm03-hi">Are you of legal drinking age?</h2>
<p id="tm-03-d" class="mb-6 text-sm text-tm03-mut">You must be 21 or older to enter this site.</p>
<div class="flex flex-wrap justify-center gap-3">
<label for="tm-03-ok" class="inline-flex items-center justify-center min-h-11 px-6 rounded-xl text-sm font-semibold cursor-pointer bg-tm03-acc text-tm03-onacc border border-tm03-acc transition-[filter] duration-200 hover:brightness-110 group-has-[#tm-03-ok:focus-visible]:outline-2 group-has-[#tm-03-ok:focus-visible]:outline-tm03-acc group-has-[#tm-03-ok:focus-visible]:outline-offset-[3px]">Yes, I am 21+</label>
<a href="https://responsibility.org" class="inline-flex items-center justify-center min-h-11 px-6 rounded-xl text-sm font-semibold no-underline text-tm03-mut border border-tm03-edge transition-colors duration-200 hover:text-tm03-ink hover:border-tm03-accedge focus-visible:outline-2 focus-visible:outline-tm03-acc focus-visible:outline-offset-[3px]">No, exit</a>
</div>
<p class="mt-5 text-xs tracking-[0.06em] text-tm03-faint">Press <kbd class="px-1.5 py-0.5 rounded border border-tm03-edge text-[11px]">Space</kbd> to confirm · please enjoy responsibly.</p>
</div>
</div>
</section>@import "tailwindcss";
@theme {
--color-tm03-bg: #0b0805;
--color-tm03-bg: oklch(0.15 0.015 70);
--color-tm03-panel: #1a1309;
--color-tm03-panel: oklch(0.21 0.025 70);
--color-tm03-tile: #241a0f;
--color-tm03-tile: oklch(0.25 0.03 70);
--color-tm03-edge: #3a2c1a;
--color-tm03-edge: oklch(0.32 0.035 70);
--color-tm03-hi: #f3e9d8;
--color-tm03-hi: oklch(0.93 0.025 80);
--color-tm03-ink: #d9cdb8;
--color-tm03-ink: oklch(0.85 0.03 80);
--color-tm03-mut: #b6a894;
--color-tm03-mut: oklch(0.73 0.03 80);
--color-tm03-faint: #8a7b64;
--color-tm03-faint: oklch(0.6 0.03 80);
--color-tm03-acc: #e0a04d;
--color-tm03-acc: oklch(0.76 0.13 70);
--color-tm03-accedge: #a87a3c;
--color-tm03-accedge: oklch(0.6 0.1 70);
--color-tm03-onacc: #1c1206;
--color-tm03-onacc: oklch(0.2 0.03 70);
--color-tm03-scrim: #080604c7;
--color-tm03-scrim: oklch(0.13 0.012 70 / 0.78);
--font-tm03-sans: 'Space Grotesk', system-ui, sans-serif;
}
.tm-03 {
width: 100%;
}@import "tailwindcss";
@theme {
--color-tm03-bg: #0b0805;
--color-tm03-bg: oklch(0.15 0.015 70);
--color-tm03-panel: #1a1309;
--color-tm03-panel: oklch(0.21 0.025 70);
--color-tm03-tile: #241a0f;
--color-tm03-tile: oklch(0.25 0.03 70);
--color-tm03-edge: #3a2c1a;
--color-tm03-edge: oklch(0.32 0.035 70);
--color-tm03-hi: #f3e9d8;
--color-tm03-hi: oklch(0.93 0.025 80);
--color-tm03-ink: #d9cdb8;
--color-tm03-ink: oklch(0.85 0.03 80);
--color-tm03-mut: #b6a894;
--color-tm03-mut: oklch(0.73 0.03 80);
--color-tm03-faint: #8a7b64;
--color-tm03-faint: oklch(0.6 0.03 80);
--color-tm03-acc: #e0a04d;
--color-tm03-acc: oklch(0.76 0.13 70);
--color-tm03-accedge: #a87a3c;
--color-tm03-accedge: oklch(0.6 0.1 70);
--color-tm03-onacc: #1c1206;
--color-tm03-onacc: oklch(0.2 0.03 70);
--color-tm03-scrim: #080604c7;
--color-tm03-scrim: oklch(0.13 0.012 70 / 0.78);
--font-tm03-sans: 'Space Grotesk', system-ui, sans-serif;
}
.tm-03 {
width: 100%;
}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-tm03-bg: #0b0805;
--color-tm03-bg: oklch(0.15 0.015 70);
--color-tm03-panel: #1a1309;
--color-tm03-panel: oklch(0.21 0.025 70);
--color-tm03-tile: #241a0f;
--color-tm03-tile: oklch(0.25 0.03 70);
--color-tm03-edge: #3a2c1a;
--color-tm03-edge: oklch(0.32 0.035 70);
--color-tm03-hi: #f3e9d8;
--color-tm03-hi: oklch(0.93 0.025 80);
--color-tm03-ink: #d9cdb8;
--color-tm03-ink: oklch(0.85 0.03 80);
--color-tm03-mut: #b6a894;
--color-tm03-mut: oklch(0.73 0.03 80);
--color-tm03-faint: #8a7b64;
--color-tm03-faint: oklch(0.6 0.03 80);
--color-tm03-acc: #e0a04d;
--color-tm03-acc: oklch(0.76 0.13 70);
--color-tm03-accedge: #a87a3c;
--color-tm03-accedge: oklch(0.6 0.1 70);
--color-tm03-onacc: #1c1206;
--color-tm03-onacc: oklch(0.2 0.03 70);
--color-tm03-scrim: #080604c7;
--color-tm03-scrim: oklch(0.13 0.012 70 / 0.78);
--font-tm03-sans: 'Space Grotesk', system-ui, sans-serif;
}
.tm-03 {
width: 100%;
}@import "tailwindcss";
@theme {
--color-tm03-bg: #0b0805;
--color-tm03-bg: oklch(0.15 0.015 70);
--color-tm03-panel: #1a1309;
--color-tm03-panel: oklch(0.21 0.025 70);
--color-tm03-tile: #241a0f;
--color-tm03-tile: oklch(0.25 0.03 70);
--color-tm03-edge: #3a2c1a;
--color-tm03-edge: oklch(0.32 0.035 70);
--color-tm03-hi: #f3e9d8;
--color-tm03-hi: oklch(0.93 0.025 80);
--color-tm03-ink: #d9cdb8;
--color-tm03-ink: oklch(0.85 0.03 80);
--color-tm03-mut: #b6a894;
--color-tm03-mut: oklch(0.73 0.03 80);
--color-tm03-faint: #8a7b64;
--color-tm03-faint: oklch(0.6 0.03 80);
--color-tm03-acc: #e0a04d;
--color-tm03-acc: oklch(0.76 0.13 70);
--color-tm03-accedge: #a87a3c;
--color-tm03-accedge: oklch(0.6 0.1 70);
--color-tm03-onacc: #1c1206;
--color-tm03-onacc: oklch(0.2 0.03 70);
--color-tm03-scrim: #080604c7;
--color-tm03-scrim: oklch(0.13 0.012 70 / 0.78);
--font-tm03-sans: 'Space Grotesk', system-ui, sans-serif;
}
.tm-03 {
width: 100%;
}<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&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&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
.cm-03:has(#cm-03-ok:checked) .cm-03__contentand its twin for the gate becomegroup-has-checked:andpeer-checked:— two variants, no ids in selectors, and each sits on the element it changes.- UI change from the CSS original: the confirm control is no longer a
role="button"label with a dead Enter key. The real checkbox stays focusable viasr-only, Space confirms, the ring renders on the visible button throughpeer-focus-visible:, and a hint line says so. - Both buttons moved to
min-h-11, and the gate's exit isopacity+visibilityrather thanvisibilityalone, so the dismissal reads as a transition instead of a cut.
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 Age Verification Gate with :has() and Zero JavaScript
Source: https://codefronts.com/components/tailwind-css-modals/tailwind-age-verification-gate-has-checked-no-javascript/
A full-screen entry gate that blurs and blocks the page until the visitor confirms their age — still zero JavaScript, now keyboard-operable. The port reads one checkbox from two directions: has-checked: on the wrapper un-blurs the article, and peer-checked: on the gate fades it out.
## HTML
```html
<section class="tm-03 group relative w-full min-h-svh overflow-hidden bg-tm03-bg font-tm03-sans">
<article class="min-h-svh flex flex-col items-center justify-center gap-5 p-10 text-center blur-[9px] scale-[1.02] transition-[filter,transform] duration-500 group-has-checked:blur-none group-has-checked:scale-100 motion-reduce:transition-none">
<h1 class="text-[clamp(38px,9vw,74px)] font-extrabold tracking-[0.12em] text-tm03-hi">CASK & OAK</h1>
<p class="max-w-[44ch] text-base leading-relaxed text-tm03-mut">Small-batch single malt, aged in highland oak. Explore the reserve collection.</p>
<div aria-hidden="true" class="flex gap-4 mt-2">
<span class="w-[90px] h-[120px] rounded-xl border border-tm03-edge bg-tm03-tile"></span>
<span class="w-[90px] h-[120px] rounded-xl border border-tm03-edge bg-tm03-tile"></span>
<span class="w-[90px] h-[120px] rounded-xl border border-tm03-edge bg-tm03-tile"></span>
</div>
</article>
<input type="checkbox" id="tm-03-ok" class="peer sr-only">
<div role="dialog" aria-modal="true" aria-labelledby="tm-03-t" aria-describedby="tm-03-d" class="fixed inset-0 z-10 grid place-items-center p-6 bg-tm03-scrim backdrop-blur-[3px] transition-[opacity,visibility] duration-[450ms] peer-checked:opacity-0 peer-checked:invisible motion-reduce:transition-none">
<div class="w-[min(430px,94vw)] px-8 py-10 text-center rounded-[20px] bg-tm03-panel border border-tm03-edge shadow-[0_40px_90px_-30px_#000]">
<div aria-hidden="true" class="grid place-items-center size-20 mx-auto mb-5 rounded-full border-2 border-tm03-accedge text-[26px] font-extrabold text-tm03-acc shadow-[0_0_40px_-10px_var(--color-tm03-acc),inset_0_0_24px_-12px_var(--color-tm03-acc)]">21+</div>
<h2 id="tm-03-t" class="mb-2.5 text-[22px] font-bold text-tm03-hi">Are you of legal drinking age?</h2>
<p id="tm-03-d" class="mb-6 text-sm text-tm03-mut">You must be 21 or older to enter this site.</p>
<div class="flex flex-wrap justify-center gap-3">
<label for="tm-03-ok" class="inline-flex items-center justify-center min-h-11 px-6 rounded-xl text-sm font-semibold cursor-pointer bg-tm03-acc text-tm03-onacc border border-tm03-acc transition-[filter] duration-200 hover:brightness-110 group-has-[#tm-03-ok:focus-visible]:outline-2 group-has-[#tm-03-ok:focus-visible]:outline-tm03-acc group-has-[#tm-03-ok:focus-visible]:outline-offset-[3px]">Yes, I am 21+</label>
<a href="https://responsibility.org" class="inline-flex items-center justify-center min-h-11 px-6 rounded-xl text-sm font-semibold no-underline text-tm03-mut border border-tm03-edge transition-colors duration-200 hover:text-tm03-ink hover:border-tm03-accedge focus-visible:outline-2 focus-visible:outline-tm03-acc focus-visible:outline-offset-[3px]">No, exit</a>
</div>
<p class="mt-5 text-xs tracking-[0.06em] text-tm03-faint">Press <kbd class="px-1.5 py-0.5 rounded border border-tm03-edge text-[11px]">Space</kbd> to confirm · please enjoy responsibly.</p>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tm03-bg: #0b0805;
--color-tm03-bg: oklch(0.15 0.015 70);
--color-tm03-panel: #1a1309;
--color-tm03-panel: oklch(0.21 0.025 70);
--color-tm03-tile: #241a0f;
--color-tm03-tile: oklch(0.25 0.03 70);
--color-tm03-edge: #3a2c1a;
--color-tm03-edge: oklch(0.32 0.035 70);
--color-tm03-hi: #f3e9d8;
--color-tm03-hi: oklch(0.93 0.025 80);
--color-tm03-ink: #d9cdb8;
--color-tm03-ink: oklch(0.85 0.03 80);
--color-tm03-mut: #b6a894;
--color-tm03-mut: oklch(0.73 0.03 80);
--color-tm03-faint: #8a7b64;
--color-tm03-faint: oklch(0.6 0.03 80);
--color-tm03-acc: #e0a04d;
--color-tm03-acc: oklch(0.76 0.13 70);
--color-tm03-accedge: #a87a3c;
--color-tm03-accedge: oklch(0.6 0.1 70);
--color-tm03-onacc: #1c1206;
--color-tm03-onacc: oklch(0.2 0.03 70);
--color-tm03-scrim: #080604c7;
--color-tm03-scrim: oklch(0.13 0.012 70 / 0.78);
--font-tm03-sans: 'Space Grotesk', system-ui, sans-serif;
}
.tm-03 {
width: 100%;
}
```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 Age Verification Gate with :has() and Zero JavaScript
Source: https://codefronts.com/components/tailwind-css-modals/tailwind-age-verification-gate-has-checked-no-javascript/
A full-screen entry gate that blurs and blocks the page until the visitor confirms their age — still zero JavaScript, now keyboard-operable. The port reads one checkbox from two directions: has-checked: on the wrapper un-blurs the article, and peer-checked: on the gate fades it out.
## HTML
```html
<section class="tm-03 group relative w-full min-h-svh overflow-hidden bg-tm03-bg font-tm03-sans">
<article class="min-h-svh flex flex-col items-center justify-center gap-5 p-10 text-center blur-[9px] scale-[1.02] transition-[filter,transform] duration-500 group-has-checked:blur-none group-has-checked:scale-100 motion-reduce:transition-none">
<h1 class="text-[clamp(38px,9vw,74px)] font-extrabold tracking-[0.12em] text-tm03-hi">CASK & OAK</h1>
<p class="max-w-[44ch] text-base leading-relaxed text-tm03-mut">Small-batch single malt, aged in highland oak. Explore the reserve collection.</p>
<div aria-hidden="true" class="flex gap-4 mt-2">
<span class="w-[90px] h-[120px] rounded-xl border border-tm03-edge bg-tm03-tile"></span>
<span class="w-[90px] h-[120px] rounded-xl border border-tm03-edge bg-tm03-tile"></span>
<span class="w-[90px] h-[120px] rounded-xl border border-tm03-edge bg-tm03-tile"></span>
</div>
</article>
<input type="checkbox" id="tm-03-ok" class="peer sr-only">
<div role="dialog" aria-modal="true" aria-labelledby="tm-03-t" aria-describedby="tm-03-d" class="fixed inset-0 z-10 grid place-items-center p-6 bg-tm03-scrim backdrop-blur-[3px] transition-[opacity,visibility] duration-[450ms] peer-checked:opacity-0 peer-checked:invisible motion-reduce:transition-none">
<div class="w-[min(430px,94vw)] px-8 py-10 text-center rounded-[20px] bg-tm03-panel border border-tm03-edge shadow-[0_40px_90px_-30px_#000]">
<div aria-hidden="true" class="grid place-items-center size-20 mx-auto mb-5 rounded-full border-2 border-tm03-accedge text-[26px] font-extrabold text-tm03-acc shadow-[0_0_40px_-10px_var(--color-tm03-acc),inset_0_0_24px_-12px_var(--color-tm03-acc)]">21+</div>
<h2 id="tm-03-t" class="mb-2.5 text-[22px] font-bold text-tm03-hi">Are you of legal drinking age?</h2>
<p id="tm-03-d" class="mb-6 text-sm text-tm03-mut">You must be 21 or older to enter this site.</p>
<div class="flex flex-wrap justify-center gap-3">
<label for="tm-03-ok" class="inline-flex items-center justify-center min-h-11 px-6 rounded-xl text-sm font-semibold cursor-pointer bg-tm03-acc text-tm03-onacc border border-tm03-acc transition-[filter] duration-200 hover:brightness-110 group-has-[#tm-03-ok:focus-visible]:outline-2 group-has-[#tm-03-ok:focus-visible]:outline-tm03-acc group-has-[#tm-03-ok:focus-visible]:outline-offset-[3px]">Yes, I am 21+</label>
<a href="https://responsibility.org" class="inline-flex items-center justify-center min-h-11 px-6 rounded-xl text-sm font-semibold no-underline text-tm03-mut border border-tm03-edge transition-colors duration-200 hover:text-tm03-ink hover:border-tm03-accedge focus-visible:outline-2 focus-visible:outline-tm03-acc focus-visible:outline-offset-[3px]">No, exit</a>
</div>
<p class="mt-5 text-xs tracking-[0.06em] text-tm03-faint">Press <kbd class="px-1.5 py-0.5 rounded border border-tm03-edge text-[11px]">Space</kbd> to confirm · please enjoy responsibly.</p>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tm03-bg: #0b0805;
--color-tm03-bg: oklch(0.15 0.015 70);
--color-tm03-panel: #1a1309;
--color-tm03-panel: oklch(0.21 0.025 70);
--color-tm03-tile: #241a0f;
--color-tm03-tile: oklch(0.25 0.03 70);
--color-tm03-edge: #3a2c1a;
--color-tm03-edge: oklch(0.32 0.035 70);
--color-tm03-hi: #f3e9d8;
--color-tm03-hi: oklch(0.93 0.025 80);
--color-tm03-ink: #d9cdb8;
--color-tm03-ink: oklch(0.85 0.03 80);
--color-tm03-mut: #b6a894;
--color-tm03-mut: oklch(0.73 0.03 80);
--color-tm03-faint: #8a7b64;
--color-tm03-faint: oklch(0.6 0.03 80);
--color-tm03-acc: #e0a04d;
--color-tm03-acc: oklch(0.76 0.13 70);
--color-tm03-accedge: #a87a3c;
--color-tm03-accedge: oklch(0.6 0.1 70);
--color-tm03-onacc: #1c1206;
--color-tm03-onacc: oklch(0.2 0.03 70);
--color-tm03-scrim: #080604c7;
--color-tm03-scrim: oklch(0.13 0.012 70 / 0.78);
--font-tm03-sans: 'Space Grotesk', system-ui, sans-serif;
}
.tm-03 {
width: 100%;
}
```How this works
One checkbox, read from both ends. The hidden input sits between the article and the gate. The wrapper carries group-free has-checked: utilities on the article (un-blur), while the gate is the checkbox's peer (fade out). Neither needs a selector written anywhere but on the element it affects.
The keyboard fix is the port's real change. The CSS original used <label role="button" tabindex="0"> — clickable, but Enter does nothing on a label, so keyboard users were stuck. Here the checkbox itself stays in the tab order (sr-only is focusable; hidden would not be), Space confirms, and peer-focus-visible: paints the ring on the visible button.
The blur is a tease, not a lock. blur-[9px] scale-[1.02] on the article keeps the layout intact while making the copy unreadable; the slight scale hides the blur's soft edges at the viewport boundary.
Make it yours
- Swap the 21+ mark and copy for your jurisdiction; the ring is
size-20 rounded-full border-2. - Persist the answer with three lines of JS writing
localStorageand settingcheckedon load. - Replace yes/no with a date-of-birth
<form>if your regulator requires it — then the gate needs JS and should become a real<dialog>.
Gotchas — read before shipping
- A CSS gate is not a focus trap: Tab still reaches the blurred article behind it. For legally binding gates use
showModal()(demos 01 and 11), which inerts the background. - Never use
hiddenordisplay:noneon the driving checkbox — it leaves the tab order and the gate becomes mouse-only again.sr-onlyis the only correct hiding utility here. - This blocks rendering, not data. The content is in the DOM; it is a UX gate, never a security boundary.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
:has() drives the un-blur (Firefox 121). Without it the article stays blurred behind a dismissible gate, so pair with a JS fallback if you must support older engines.