11 / 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 Destructive Delete Modal with Type-to-Confirm Gate
The friction that prevents an accidental account deletion: plain-language consequences, a keyword the user must type, and a Delete button that stays genuinely disabled until they do. disabled:opacity-40 disabled:cursor-not-allowed styles the gate; the attribute enforces it.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tm-11 w-full min-h-svh grid place-items-center bg-tm11-bg font-tm11-sans px-6 py-10">
<button type="button" data-tm11-open class="inline-flex items-center justify-center min-h-11 px-6 rounded-xl text-[15px] font-semibold text-tm11-dangerink bg-tm11-dangertint border border-tm11-dangeredge cursor-pointer transition-colors duration-200 hover:bg-tm11-dangerhover hover:border-tm11-danger focus-visible:outline-2 focus-visible:outline-tm11-danger focus-visible:outline-offset-2">Delete account</button>
<dialog id="tm-11-dlg" aria-labelledby="tm-11-title" aria-describedby="tm-11-desc" class="m-auto w-[min(420px,94vw)] p-0 border-0 rounded-[18px] text-tm11-ink bg-tm11-card shadow-[0_40px_90px_-30px_#000,0_0_0_1px_var(--color-tm11-edge)] opacity-0 translate-y-2.5 scale-[0.97] transition-[opacity,translate,scale,overlay,display] duration-[260ms] transition-discrete open:opacity-100 open:translate-y-0 open:scale-100 open:starting:opacity-0 open:starting:translate-y-2.5 open:starting:scale-[0.97] open:backdrop:bg-tm11-scrim open:backdrop:backdrop-blur-[5px] motion-reduce:transition-none">
<div class="px-8 pt-8 pb-6">
<div aria-hidden="true" class="grid place-items-center size-14 mb-4.5 rounded-[14px] text-[26px] text-tm11-danger bg-tm11-dangertint shadow-[inset_0_0_0_1px_var(--color-tm11-dangeredge)]">⚠</div>
<h2 id="tm-11-title" class="mb-2.5 text-[22px] font-bold text-tm11-hi">Delete your account?</h2>
<p id="tm-11-desc" class="mb-5 text-sm leading-relaxed text-tm11-mut">This <b class="font-semibold text-tm11-hi">permanently</b> deletes your account, 3 projects and all associated data. This action <b class="font-semibold text-tm11-hi">cannot be undone</b>.</p>
<label for="tm-11-confirm" class="block mb-2 text-[12.5px] text-tm11-mut">Type <code class="px-1.5 py-0.5 rounded bg-tm11-code font-tm11-mono font-bold tracking-[0.06em] text-tm11-dangerink">DELETE</code> to confirm</label>
<input id="tm-11-confirm" type="text" autocomplete="off" placeholder="DELETE" class="w-full min-h-12 px-3.5 mb-5 rounded-[10px] border border-tm11-edge bg-tm11-well font-tm11-mono text-[15px] tracking-[0.1em] text-tm11-hi placeholder:text-tm11-faint transition-[border-color,box-shadow] duration-200 focus:outline-none focus:border-tm11-danger focus:shadow-[0_0_0_3px_var(--color-tm11-ring)]">
<div class="grid grid-cols-2 gap-3">
<button type="button" data-tm11-cancel autofocus class="min-h-12 rounded-xl text-[14.5px] font-bold cursor-pointer text-tm11-ink bg-tm11-btn border border-tm11-btnedge transition-colors duration-200 hover:bg-tm11-btnhover hover:border-tm11-edge focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2">Cancel</button>
<button type="button" data-tm11-delete disabled class="min-h-12 rounded-xl text-[14.5px] font-bold cursor-pointer border-0 bg-tm11-danger text-white transition-[filter,opacity] duration-200 hover:brightness-110 disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:brightness-100 focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2">Delete account</button>
</div>
</div>
</dialog>
</section><section class="tm-11 w-full min-h-svh grid place-items-center bg-tm11-bg font-tm11-sans px-6 py-10">
<button type="button" data-tm11-open class="inline-flex items-center justify-center min-h-11 px-6 rounded-xl text-[15px] font-semibold text-tm11-dangerink bg-tm11-dangertint border border-tm11-dangeredge cursor-pointer transition-colors duration-200 hover:bg-tm11-dangerhover hover:border-tm11-danger focus-visible:outline-2 focus-visible:outline-tm11-danger focus-visible:outline-offset-2">Delete account</button>
<dialog id="tm-11-dlg" aria-labelledby="tm-11-title" aria-describedby="tm-11-desc" class="m-auto w-[min(420px,94vw)] p-0 border-0 rounded-[18px] text-tm11-ink bg-tm11-card shadow-[0_40px_90px_-30px_#000,0_0_0_1px_var(--color-tm11-edge)] opacity-0 translate-y-2.5 scale-[0.97] transition-[opacity,translate,scale,overlay,display] duration-[260ms] transition-discrete open:opacity-100 open:translate-y-0 open:scale-100 open:starting:opacity-0 open:starting:translate-y-2.5 open:starting:scale-[0.97] open:backdrop:bg-tm11-scrim open:backdrop:backdrop-blur-[5px] motion-reduce:transition-none">
<div class="px-8 pt-8 pb-6">
<div aria-hidden="true" class="grid place-items-center size-14 mb-4.5 rounded-[14px] text-[26px] text-tm11-danger bg-tm11-dangertint shadow-[inset_0_0_0_1px_var(--color-tm11-dangeredge)]">⚠</div>
<h2 id="tm-11-title" class="mb-2.5 text-[22px] font-bold text-tm11-hi">Delete your account?</h2>
<p id="tm-11-desc" class="mb-5 text-sm leading-relaxed text-tm11-mut">This <b class="font-semibold text-tm11-hi">permanently</b> deletes your account, 3 projects and all associated data. This action <b class="font-semibold text-tm11-hi">cannot be undone</b>.</p>
<label for="tm-11-confirm" class="block mb-2 text-[12.5px] text-tm11-mut">Type <code class="px-1.5 py-0.5 rounded bg-tm11-code font-tm11-mono font-bold tracking-[0.06em] text-tm11-dangerink">DELETE</code> to confirm</label>
<input id="tm-11-confirm" type="text" autocomplete="off" placeholder="DELETE" class="w-full min-h-12 px-3.5 mb-5 rounded-[10px] border border-tm11-edge bg-tm11-well font-tm11-mono text-[15px] tracking-[0.1em] text-tm11-hi placeholder:text-tm11-faint transition-[border-color,box-shadow] duration-200 focus:outline-none focus:border-tm11-danger focus:shadow-[0_0_0_3px_var(--color-tm11-ring)]">
<div class="grid grid-cols-2 gap-3">
<button type="button" data-tm11-cancel autofocus class="min-h-12 rounded-xl text-[14.5px] font-bold cursor-pointer text-tm11-ink bg-tm11-btn border border-tm11-btnedge transition-colors duration-200 hover:bg-tm11-btnhover hover:border-tm11-edge focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2">Cancel</button>
<button type="button" data-tm11-delete disabled class="min-h-12 rounded-xl text-[14.5px] font-bold cursor-pointer border-0 bg-tm11-danger text-white transition-[filter,opacity] duration-200 hover:brightness-110 disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:brightness-100 focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2">Delete account</button>
</div>
</div>
</dialog>
</section>@import "tailwindcss";
@theme {
--color-tm11-bg: #080706;
--color-tm11-bg: oklch(0.14 0.008 40);
--color-tm11-card: #1a1513;
--color-tm11-card: oklch(0.21 0.015 40);
--color-tm11-well: #0e0a09;
--color-tm11-well: oklch(0.16 0.01 40);
--color-tm11-code: #241816;
--color-tm11-code: oklch(0.24 0.025 30);
--color-tm11-btn: #1c1614;
--color-tm11-btn: oklch(0.22 0.012 40);
--color-tm11-btnhover: #241c19;
--color-tm11-btnhover: oklch(0.26 0.015 40);
--color-tm11-btnedge: #3a2b28;
--color-tm11-btnedge: oklch(0.32 0.02 30);
--color-tm11-edge: #43302b;
--color-tm11-edge: oklch(0.36 0.025 30);
--color-tm11-hi: #fbeeeb;
--color-tm11-hi: oklch(0.95 0.012 30);
--color-tm11-ink: #e6d7d3;
--color-tm11-ink: oklch(0.89 0.015 30);
--color-tm11-mut: #c2aca7;
--color-tm11-mut: oklch(0.76 0.02 30);
--color-tm11-faint: #8a706b;
--color-tm11-faint: oklch(0.58 0.025 30);
--color-tm11-danger: #e8503f;
--color-tm11-danger: oklch(0.62 0.22 27);
--color-tm11-dangerink: #ffd9d4;
--color-tm11-dangerink: oklch(0.9 0.05 25);
--color-tm11-dangertint: #2b1513;
--color-tm11-dangertint: oklch(0.25 0.06 27);
--color-tm11-dangerhover: #3a1c18;
--color-tm11-dangerhover: oklch(0.3 0.08 27);
--color-tm11-dangeredge: #7a2e26;
--color-tm11-dangeredge: oklch(0.42 0.13 27);
--color-tm11-ring: #e8503f42;
--color-tm11-ring: oklch(0.62 0.22 27 / 0.26);
--color-tm11-scrim: #0604039e;
--color-tm11-scrim: oklch(0.12 0.008 30 / 0.62);
--font-tm11-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tm11-mono: 'JetBrains Mono', ui-monospace, monospace;
}
.tm-11 {
width: 100%;
}
#tm-11-dlg::backdrop {
background: oklch(0.12 0.008 30 / 0);
backdrop-filter: blur(0px);
transition: background .3s, backdrop-filter .3s, overlay .3s allow-discrete, display .3s allow-discrete;
}
@starting-style {
#tm-11-dlg[open]::backdrop {
background: oklch(0.12 0.008 30 / 0);
backdrop-filter: blur(0px);
}
}@import "tailwindcss";
@theme {
--color-tm11-bg: #080706;
--color-tm11-bg: oklch(0.14 0.008 40);
--color-tm11-card: #1a1513;
--color-tm11-card: oklch(0.21 0.015 40);
--color-tm11-well: #0e0a09;
--color-tm11-well: oklch(0.16 0.01 40);
--color-tm11-code: #241816;
--color-tm11-code: oklch(0.24 0.025 30);
--color-tm11-btn: #1c1614;
--color-tm11-btn: oklch(0.22 0.012 40);
--color-tm11-btnhover: #241c19;
--color-tm11-btnhover: oklch(0.26 0.015 40);
--color-tm11-btnedge: #3a2b28;
--color-tm11-btnedge: oklch(0.32 0.02 30);
--color-tm11-edge: #43302b;
--color-tm11-edge: oklch(0.36 0.025 30);
--color-tm11-hi: #fbeeeb;
--color-tm11-hi: oklch(0.95 0.012 30);
--color-tm11-ink: #e6d7d3;
--color-tm11-ink: oklch(0.89 0.015 30);
--color-tm11-mut: #c2aca7;
--color-tm11-mut: oklch(0.76 0.02 30);
--color-tm11-faint: #8a706b;
--color-tm11-faint: oklch(0.58 0.025 30);
--color-tm11-danger: #e8503f;
--color-tm11-danger: oklch(0.62 0.22 27);
--color-tm11-dangerink: #ffd9d4;
--color-tm11-dangerink: oklch(0.9 0.05 25);
--color-tm11-dangertint: #2b1513;
--color-tm11-dangertint: oklch(0.25 0.06 27);
--color-tm11-dangerhover: #3a1c18;
--color-tm11-dangerhover: oklch(0.3 0.08 27);
--color-tm11-dangeredge: #7a2e26;
--color-tm11-dangeredge: oklch(0.42 0.13 27);
--color-tm11-ring: #e8503f42;
--color-tm11-ring: oklch(0.62 0.22 27 / 0.26);
--color-tm11-scrim: #0604039e;
--color-tm11-scrim: oklch(0.12 0.008 30 / 0.62);
--font-tm11-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tm11-mono: 'JetBrains Mono', ui-monospace, monospace;
}
.tm-11 {
width: 100%;
}
#tm-11-dlg::backdrop {
background: oklch(0.12 0.008 30 / 0);
backdrop-filter: blur(0px);
transition: background .3s, backdrop-filter .3s, overlay .3s allow-discrete, display .3s allow-discrete;
}
@starting-style {
#tm-11-dlg[open]::backdrop {
background: oklch(0.12 0.008 30 / 0);
backdrop-filter: blur(0px);
}
}(function () {
var root = document.querySelector('.tm-11');
var dlg = root.querySelector('#tm-11-dlg');
var input = root.querySelector('#tm-11-confirm');
var del = root.querySelector('[data-tm11-delete]');
var KEYWORD = 'DELETE';
root.querySelector('[data-tm11-open]').addEventListener('click', function () {
input.value = ''; del.disabled = true; dlg.showModal(); // always reopen clean
});
root.querySelector('[data-tm11-cancel]').addEventListener('click', function () { dlg.close(); });
input.addEventListener('input', function () { del.disabled = input.value.trim() !== KEYWORD; });
del.addEventListener('click', function () {
del.textContent = 'Deleting…';
setTimeout(function () { dlg.close(); del.textContent = 'Delete account'; }, 900);
});
})();(function () {
var root = document.querySelector('.tm-11');
var dlg = root.querySelector('#tm-11-dlg');
var input = root.querySelector('#tm-11-confirm');
var del = root.querySelector('[data-tm11-delete]');
var KEYWORD = 'DELETE';
root.querySelector('[data-tm11-open]').addEventListener('click', function () {
input.value = ''; del.disabled = true; dlg.showModal(); // always reopen clean
});
root.querySelector('[data-tm11-cancel]').addEventListener('click', function () { dlg.close(); });
input.addEventListener('input', function () { del.disabled = input.value.trim() !== KEYWORD; });
del.addEventListener('click', function () {
del.textContent = 'Deleting…';
setTimeout(function () { dlg.close(); del.textContent = 'Delete account'; }, 900);
});
})();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-tm11-bg: #080706;
--color-tm11-bg: oklch(0.14 0.008 40);
--color-tm11-card: #1a1513;
--color-tm11-card: oklch(0.21 0.015 40);
--color-tm11-well: #0e0a09;
--color-tm11-well: oklch(0.16 0.01 40);
--color-tm11-code: #241816;
--color-tm11-code: oklch(0.24 0.025 30);
--color-tm11-btn: #1c1614;
--color-tm11-btn: oklch(0.22 0.012 40);
--color-tm11-btnhover: #241c19;
--color-tm11-btnhover: oklch(0.26 0.015 40);
--color-tm11-btnedge: #3a2b28;
--color-tm11-btnedge: oklch(0.32 0.02 30);
--color-tm11-edge: #43302b;
--color-tm11-edge: oklch(0.36 0.025 30);
--color-tm11-hi: #fbeeeb;
--color-tm11-hi: oklch(0.95 0.012 30);
--color-tm11-ink: #e6d7d3;
--color-tm11-ink: oklch(0.89 0.015 30);
--color-tm11-mut: #c2aca7;
--color-tm11-mut: oklch(0.76 0.02 30);
--color-tm11-faint: #8a706b;
--color-tm11-faint: oklch(0.58 0.025 30);
--color-tm11-danger: #e8503f;
--color-tm11-danger: oklch(0.62 0.22 27);
--color-tm11-dangerink: #ffd9d4;
--color-tm11-dangerink: oklch(0.9 0.05 25);
--color-tm11-dangertint: #2b1513;
--color-tm11-dangertint: oklch(0.25 0.06 27);
--color-tm11-dangerhover: #3a1c18;
--color-tm11-dangerhover: oklch(0.3 0.08 27);
--color-tm11-dangeredge: #7a2e26;
--color-tm11-dangeredge: oklch(0.42 0.13 27);
--color-tm11-ring: #e8503f42;
--color-tm11-ring: oklch(0.62 0.22 27 / 0.26);
--color-tm11-scrim: #0604039e;
--color-tm11-scrim: oklch(0.12 0.008 30 / 0.62);
--font-tm11-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tm11-mono: 'JetBrains Mono', ui-monospace, monospace;
}
.tm-11 {
width: 100%;
}
#tm-11-dlg::backdrop {
background: oklch(0.12 0.008 30 / 0);
backdrop-filter: blur(0px);
transition: background .3s, backdrop-filter .3s, overlay .3s allow-discrete, display .3s allow-discrete;
}
@starting-style {
#tm-11-dlg[open]::backdrop {
background: oklch(0.12 0.008 30 / 0);
backdrop-filter: blur(0px);
}
}@import "tailwindcss";
@theme {
--color-tm11-bg: #080706;
--color-tm11-bg: oklch(0.14 0.008 40);
--color-tm11-card: #1a1513;
--color-tm11-card: oklch(0.21 0.015 40);
--color-tm11-well: #0e0a09;
--color-tm11-well: oklch(0.16 0.01 40);
--color-tm11-code: #241816;
--color-tm11-code: oklch(0.24 0.025 30);
--color-tm11-btn: #1c1614;
--color-tm11-btn: oklch(0.22 0.012 40);
--color-tm11-btnhover: #241c19;
--color-tm11-btnhover: oklch(0.26 0.015 40);
--color-tm11-btnedge: #3a2b28;
--color-tm11-btnedge: oklch(0.32 0.02 30);
--color-tm11-edge: #43302b;
--color-tm11-edge: oklch(0.36 0.025 30);
--color-tm11-hi: #fbeeeb;
--color-tm11-hi: oklch(0.95 0.012 30);
--color-tm11-ink: #e6d7d3;
--color-tm11-ink: oklch(0.89 0.015 30);
--color-tm11-mut: #c2aca7;
--color-tm11-mut: oklch(0.76 0.02 30);
--color-tm11-faint: #8a706b;
--color-tm11-faint: oklch(0.58 0.025 30);
--color-tm11-danger: #e8503f;
--color-tm11-danger: oklch(0.62 0.22 27);
--color-tm11-dangerink: #ffd9d4;
--color-tm11-dangerink: oklch(0.9 0.05 25);
--color-tm11-dangertint: #2b1513;
--color-tm11-dangertint: oklch(0.25 0.06 27);
--color-tm11-dangerhover: #3a1c18;
--color-tm11-dangerhover: oklch(0.3 0.08 27);
--color-tm11-dangeredge: #7a2e26;
--color-tm11-dangeredge: oklch(0.42 0.13 27);
--color-tm11-ring: #e8503f42;
--color-tm11-ring: oklch(0.62 0.22 27 / 0.26);
--color-tm11-scrim: #0604039e;
--color-tm11-scrim: oklch(0.12 0.008 30 / 0.62);
--font-tm11-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tm11-mono: 'JetBrains Mono', ui-monospace, monospace;
}
.tm-11 {
width: 100%;
}
#tm-11-dlg::backdrop {
background: oklch(0.12 0.008 30 / 0);
backdrop-filter: blur(0px);
transition: background .3s, backdrop-filter .3s, overlay .3s allow-discrete, display .3s allow-discrete;
}
@starting-style {
#tm-11-dlg[open]::backdrop {
background: oklch(0.12 0.008 30 / 0);
backdrop-filter: blur(0px);
}
}<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;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&family=JetBrains+Mono:wght@400;500;700&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
.cm-11__delete:disabledand:hover:not(:disabled)becomedisabled:anddisabled:hover:— stacking the two variants is how you say "and definitely not this on hover" without a:not().- The danger palette is four tokens (
danger,dangertint,dangeredge,dangerink) rather thancolor-mix()at every use site, which is the v4 way: mix once in@theme, reference everywhere. - UI change from the CSS original: both action buttons and the confirm field moved to 48px, the confirm input is monospaced so
DELETEis unambiguous, and the<b>emphasis in the description is now weight plus colour rather than colour alone.
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 Destructive Delete Modal with Type-to-Confirm Gate
Source: https://codefronts.com/components/tailwind-css-modals/tailwind-destructive-delete-modal-type-to-confirm/
The friction that prevents an accidental account deletion: plain-language consequences, a keyword the user must type, and a Delete button that stays genuinely disabled until they do. disabled:opacity-40 disabled:cursor-not-allowed styles the gate; the attribute enforces it.
## HTML
```html
<section class="tm-11 w-full min-h-svh grid place-items-center bg-tm11-bg font-tm11-sans px-6 py-10">
<button type="button" data-tm11-open class="inline-flex items-center justify-center min-h-11 px-6 rounded-xl text-[15px] font-semibold text-tm11-dangerink bg-tm11-dangertint border border-tm11-dangeredge cursor-pointer transition-colors duration-200 hover:bg-tm11-dangerhover hover:border-tm11-danger focus-visible:outline-2 focus-visible:outline-tm11-danger focus-visible:outline-offset-2">Delete account</button>
<dialog id="tm-11-dlg" aria-labelledby="tm-11-title" aria-describedby="tm-11-desc" class="m-auto w-[min(420px,94vw)] p-0 border-0 rounded-[18px] text-tm11-ink bg-tm11-card shadow-[0_40px_90px_-30px_#000,0_0_0_1px_var(--color-tm11-edge)] opacity-0 translate-y-2.5 scale-[0.97] transition-[opacity,translate,scale,overlay,display] duration-[260ms] transition-discrete open:opacity-100 open:translate-y-0 open:scale-100 open:starting:opacity-0 open:starting:translate-y-2.5 open:starting:scale-[0.97] open:backdrop:bg-tm11-scrim open:backdrop:backdrop-blur-[5px] motion-reduce:transition-none">
<div class="px-8 pt-8 pb-6">
<div aria-hidden="true" class="grid place-items-center size-14 mb-4.5 rounded-[14px] text-[26px] text-tm11-danger bg-tm11-dangertint shadow-[inset_0_0_0_1px_var(--color-tm11-dangeredge)]">⚠</div>
<h2 id="tm-11-title" class="mb-2.5 text-[22px] font-bold text-tm11-hi">Delete your account?</h2>
<p id="tm-11-desc" class="mb-5 text-sm leading-relaxed text-tm11-mut">This <b class="font-semibold text-tm11-hi">permanently</b> deletes your account, 3 projects and all associated data. This action <b class="font-semibold text-tm11-hi">cannot be undone</b>.</p>
<label for="tm-11-confirm" class="block mb-2 text-[12.5px] text-tm11-mut">Type <code class="px-1.5 py-0.5 rounded bg-tm11-code font-tm11-mono font-bold tracking-[0.06em] text-tm11-dangerink">DELETE</code> to confirm</label>
<input id="tm-11-confirm" type="text" autocomplete="off" placeholder="DELETE" class="w-full min-h-12 px-3.5 mb-5 rounded-[10px] border border-tm11-edge bg-tm11-well font-tm11-mono text-[15px] tracking-[0.1em] text-tm11-hi placeholder:text-tm11-faint transition-[border-color,box-shadow] duration-200 focus:outline-none focus:border-tm11-danger focus:shadow-[0_0_0_3px_var(--color-tm11-ring)]">
<div class="grid grid-cols-2 gap-3">
<button type="button" data-tm11-cancel autofocus class="min-h-12 rounded-xl text-[14.5px] font-bold cursor-pointer text-tm11-ink bg-tm11-btn border border-tm11-btnedge transition-colors duration-200 hover:bg-tm11-btnhover hover:border-tm11-edge focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2">Cancel</button>
<button type="button" data-tm11-delete disabled class="min-h-12 rounded-xl text-[14.5px] font-bold cursor-pointer border-0 bg-tm11-danger text-white transition-[filter,opacity] duration-200 hover:brightness-110 disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:brightness-100 focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2">Delete account</button>
</div>
</div>
</dialog>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tm11-bg: #080706;
--color-tm11-bg: oklch(0.14 0.008 40);
--color-tm11-card: #1a1513;
--color-tm11-card: oklch(0.21 0.015 40);
--color-tm11-well: #0e0a09;
--color-tm11-well: oklch(0.16 0.01 40);
--color-tm11-code: #241816;
--color-tm11-code: oklch(0.24 0.025 30);
--color-tm11-btn: #1c1614;
--color-tm11-btn: oklch(0.22 0.012 40);
--color-tm11-btnhover: #241c19;
--color-tm11-btnhover: oklch(0.26 0.015 40);
--color-tm11-btnedge: #3a2b28;
--color-tm11-btnedge: oklch(0.32 0.02 30);
--color-tm11-edge: #43302b;
--color-tm11-edge: oklch(0.36 0.025 30);
--color-tm11-hi: #fbeeeb;
--color-tm11-hi: oklch(0.95 0.012 30);
--color-tm11-ink: #e6d7d3;
--color-tm11-ink: oklch(0.89 0.015 30);
--color-tm11-mut: #c2aca7;
--color-tm11-mut: oklch(0.76 0.02 30);
--color-tm11-faint: #8a706b;
--color-tm11-faint: oklch(0.58 0.025 30);
--color-tm11-danger: #e8503f;
--color-tm11-danger: oklch(0.62 0.22 27);
--color-tm11-dangerink: #ffd9d4;
--color-tm11-dangerink: oklch(0.9 0.05 25);
--color-tm11-dangertint: #2b1513;
--color-tm11-dangertint: oklch(0.25 0.06 27);
--color-tm11-dangerhover: #3a1c18;
--color-tm11-dangerhover: oklch(0.3 0.08 27);
--color-tm11-dangeredge: #7a2e26;
--color-tm11-dangeredge: oklch(0.42 0.13 27);
--color-tm11-ring: #e8503f42;
--color-tm11-ring: oklch(0.62 0.22 27 / 0.26);
--color-tm11-scrim: #0604039e;
--color-tm11-scrim: oklch(0.12 0.008 30 / 0.62);
--font-tm11-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tm11-mono: 'JetBrains Mono', ui-monospace, monospace;
}
.tm-11 {
width: 100%;
}
#tm-11-dlg::backdrop {
background: oklch(0.12 0.008 30 / 0);
backdrop-filter: blur(0px);
transition: background .3s, backdrop-filter .3s, overlay .3s allow-discrete, display .3s allow-discrete;
}
@starting-style {
#tm-11-dlg[open]::backdrop {
background: oklch(0.12 0.008 30 / 0);
backdrop-filter: blur(0px);
}
}
```
## JavaScript
```js
(function () {
var root = document.querySelector('.tm-11');
var dlg = root.querySelector('#tm-11-dlg');
var input = root.querySelector('#tm-11-confirm');
var del = root.querySelector('[data-tm11-delete]');
var KEYWORD = 'DELETE';
root.querySelector('[data-tm11-open]').addEventListener('click', function () {
input.value = ''; del.disabled = true; dlg.showModal(); // always reopen clean
});
root.querySelector('[data-tm11-cancel]').addEventListener('click', function () { dlg.close(); });
input.addEventListener('input', function () { del.disabled = input.value.trim() !== KEYWORD; });
del.addEventListener('click', function () {
del.textContent = 'Deleting…';
setTimeout(function () { dlg.close(); del.textContent = 'Delete account'; }, 900);
});
})();
```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 Destructive Delete Modal with Type-to-Confirm Gate
Source: https://codefronts.com/components/tailwind-css-modals/tailwind-destructive-delete-modal-type-to-confirm/
The friction that prevents an accidental account deletion: plain-language consequences, a keyword the user must type, and a Delete button that stays genuinely disabled until they do. disabled:opacity-40 disabled:cursor-not-allowed styles the gate; the attribute enforces it.
## HTML
```html
<section class="tm-11 w-full min-h-svh grid place-items-center bg-tm11-bg font-tm11-sans px-6 py-10">
<button type="button" data-tm11-open class="inline-flex items-center justify-center min-h-11 px-6 rounded-xl text-[15px] font-semibold text-tm11-dangerink bg-tm11-dangertint border border-tm11-dangeredge cursor-pointer transition-colors duration-200 hover:bg-tm11-dangerhover hover:border-tm11-danger focus-visible:outline-2 focus-visible:outline-tm11-danger focus-visible:outline-offset-2">Delete account</button>
<dialog id="tm-11-dlg" aria-labelledby="tm-11-title" aria-describedby="tm-11-desc" class="m-auto w-[min(420px,94vw)] p-0 border-0 rounded-[18px] text-tm11-ink bg-tm11-card shadow-[0_40px_90px_-30px_#000,0_0_0_1px_var(--color-tm11-edge)] opacity-0 translate-y-2.5 scale-[0.97] transition-[opacity,translate,scale,overlay,display] duration-[260ms] transition-discrete open:opacity-100 open:translate-y-0 open:scale-100 open:starting:opacity-0 open:starting:translate-y-2.5 open:starting:scale-[0.97] open:backdrop:bg-tm11-scrim open:backdrop:backdrop-blur-[5px] motion-reduce:transition-none">
<div class="px-8 pt-8 pb-6">
<div aria-hidden="true" class="grid place-items-center size-14 mb-4.5 rounded-[14px] text-[26px] text-tm11-danger bg-tm11-dangertint shadow-[inset_0_0_0_1px_var(--color-tm11-dangeredge)]">⚠</div>
<h2 id="tm-11-title" class="mb-2.5 text-[22px] font-bold text-tm11-hi">Delete your account?</h2>
<p id="tm-11-desc" class="mb-5 text-sm leading-relaxed text-tm11-mut">This <b class="font-semibold text-tm11-hi">permanently</b> deletes your account, 3 projects and all associated data. This action <b class="font-semibold text-tm11-hi">cannot be undone</b>.</p>
<label for="tm-11-confirm" class="block mb-2 text-[12.5px] text-tm11-mut">Type <code class="px-1.5 py-0.5 rounded bg-tm11-code font-tm11-mono font-bold tracking-[0.06em] text-tm11-dangerink">DELETE</code> to confirm</label>
<input id="tm-11-confirm" type="text" autocomplete="off" placeholder="DELETE" class="w-full min-h-12 px-3.5 mb-5 rounded-[10px] border border-tm11-edge bg-tm11-well font-tm11-mono text-[15px] tracking-[0.1em] text-tm11-hi placeholder:text-tm11-faint transition-[border-color,box-shadow] duration-200 focus:outline-none focus:border-tm11-danger focus:shadow-[0_0_0_3px_var(--color-tm11-ring)]">
<div class="grid grid-cols-2 gap-3">
<button type="button" data-tm11-cancel autofocus class="min-h-12 rounded-xl text-[14.5px] font-bold cursor-pointer text-tm11-ink bg-tm11-btn border border-tm11-btnedge transition-colors duration-200 hover:bg-tm11-btnhover hover:border-tm11-edge focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2">Cancel</button>
<button type="button" data-tm11-delete disabled class="min-h-12 rounded-xl text-[14.5px] font-bold cursor-pointer border-0 bg-tm11-danger text-white transition-[filter,opacity] duration-200 hover:brightness-110 disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:brightness-100 focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2">Delete account</button>
</div>
</div>
</dialog>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tm11-bg: #080706;
--color-tm11-bg: oklch(0.14 0.008 40);
--color-tm11-card: #1a1513;
--color-tm11-card: oklch(0.21 0.015 40);
--color-tm11-well: #0e0a09;
--color-tm11-well: oklch(0.16 0.01 40);
--color-tm11-code: #241816;
--color-tm11-code: oklch(0.24 0.025 30);
--color-tm11-btn: #1c1614;
--color-tm11-btn: oklch(0.22 0.012 40);
--color-tm11-btnhover: #241c19;
--color-tm11-btnhover: oklch(0.26 0.015 40);
--color-tm11-btnedge: #3a2b28;
--color-tm11-btnedge: oklch(0.32 0.02 30);
--color-tm11-edge: #43302b;
--color-tm11-edge: oklch(0.36 0.025 30);
--color-tm11-hi: #fbeeeb;
--color-tm11-hi: oklch(0.95 0.012 30);
--color-tm11-ink: #e6d7d3;
--color-tm11-ink: oklch(0.89 0.015 30);
--color-tm11-mut: #c2aca7;
--color-tm11-mut: oklch(0.76 0.02 30);
--color-tm11-faint: #8a706b;
--color-tm11-faint: oklch(0.58 0.025 30);
--color-tm11-danger: #e8503f;
--color-tm11-danger: oklch(0.62 0.22 27);
--color-tm11-dangerink: #ffd9d4;
--color-tm11-dangerink: oklch(0.9 0.05 25);
--color-tm11-dangertint: #2b1513;
--color-tm11-dangertint: oklch(0.25 0.06 27);
--color-tm11-dangerhover: #3a1c18;
--color-tm11-dangerhover: oklch(0.3 0.08 27);
--color-tm11-dangeredge: #7a2e26;
--color-tm11-dangeredge: oklch(0.42 0.13 27);
--color-tm11-ring: #e8503f42;
--color-tm11-ring: oklch(0.62 0.22 27 / 0.26);
--color-tm11-scrim: #0604039e;
--color-tm11-scrim: oklch(0.12 0.008 30 / 0.62);
--font-tm11-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tm11-mono: 'JetBrains Mono', ui-monospace, monospace;
}
.tm-11 {
width: 100%;
}
#tm-11-dlg::backdrop {
background: oklch(0.12 0.008 30 / 0);
backdrop-filter: blur(0px);
transition: background .3s, backdrop-filter .3s, overlay .3s allow-discrete, display .3s allow-discrete;
}
@starting-style {
#tm-11-dlg[open]::backdrop {
background: oklch(0.12 0.008 30 / 0);
backdrop-filter: blur(0px);
}
}
```
## JavaScript
```js
(function () {
var root = document.querySelector('.tm-11');
var dlg = root.querySelector('#tm-11-dlg');
var input = root.querySelector('#tm-11-confirm');
var del = root.querySelector('[data-tm11-delete]');
var KEYWORD = 'DELETE';
root.querySelector('[data-tm11-open]').addEventListener('click', function () {
input.value = ''; del.disabled = true; dlg.showModal(); // always reopen clean
});
root.querySelector('[data-tm11-cancel]').addEventListener('click', function () { dlg.close(); });
input.addEventListener('input', function () { del.disabled = input.value.trim() !== KEYWORD; });
del.addEventListener('click', function () {
del.textContent = 'Deleting…';
setTimeout(function () { dlg.close(); del.textContent = 'Delete account'; }, 900);
});
})();
```How this works
The gate is an attribute, not a style. Every keystroke compares the trimmed value to the keyword and sets disabled. A greyed-but-clickable button is not a guard; the utilities here only describe a state the DOM already enforces, which is the correct division.
Emphasis is inverted on purpose. Cancel gets the neutral filled treatment and the autofocus; Delete gets the danger colour but starts inert. In a normal modal the primary action is the confident one — in a destructive modal the confident action is leaving.
Danger is never colour alone. The warning glyph, the word "permanently", the count of what is being destroyed and the red all say the same thing, so a colour-blind user loses none of the signal.
Make it yours
- Change the keyword to the resource's own name — typing
invoices-prodis a stronger check than typingDELETE. - Drop the gate entirely for reversible deletes; reserve typing for the irreversible ones or it becomes noise.
- For the highest stakes, replace the click with hold-to-confirm: a
pointerdowntimer plus a width-animated progress bar.
Gotchas — read before shipping
- Autofocus the safe action.
autofocuson Delete means a stray Enter destroys data, and Esc must cancel — which it does natively, and is why this is a real<dialog>. - Reset the input and re-disable the button on every open, or a reopened dialog arrives pre-confirmed from last time.
- Write the consequence concretely: "deletes your account, 3 projects and all associated data" outperforms "Are you sure?" at preventing regret.
- Compare against a trimmed value. A trailing space from a paste silently keeps the button disabled and users blame the app.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ · 117+ anim | 16.4+ · 17.5+ anim | 128+ · 129+ anim | 111+ · 117+ anim |
<dialog>, :disabled and autofocus are universal; only the enter animation needs @starting-style.