19 / 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 Confirm Delete Modal with Corner Slide-and-Spin Entrance
A destructive-action confirmation whose card swings in from the bottom-right at 3° and settles square. The port keeps the muted intensity of the original — a spring easing token, not a bounce — and states the whole entrance as open: plus open:starting: on the dialog, so the closed state, the open state and the pre-transition frame are one class list.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tw-01 w-full min-h-svh grid place-items-center bg-tw01-bg font-tw01-sans px-6 py-10">
<button type="button" data-tw01-open class="inline-flex items-center justify-center min-h-11 px-6 rounded-lg text-[15px] font-semibold text-tw01-danger bg-transparent border border-tw01-danger cursor-pointer transition-colors duration-200 hover:bg-tw01-danger hover:text-white focus-visible:outline-2 focus-visible:outline-tw01-danger focus-visible:outline-offset-2">Delete report</button>
<dialog id="tw-01-dlg" aria-labelledby="tw-01-title" aria-describedby="tw-01-desc" class="m-auto w-[min(360px,94vw)] p-0 border-0 rounded-[14px] text-center text-tw01-ink bg-tw01-card shadow-[0_30px_60px_-20px_#00000066] origin-bottom-right opacity-0 transition-[opacity,translate,rotate,scale,overlay,display] duration-[400ms] ease-[var(--ease-tw01-spring)] transition-discrete open:opacity-100 open:translate-x-0 open:translate-y-0 open:rotate-0 open:scale-100 open:starting:opacity-0 open:starting:translate-x-6 open:starting:translate-y-8 open:starting:rotate-3 open:starting:scale-[0.96] open:backdrop:bg-tw01-scrim open:backdrop:backdrop-blur-[4px] motion-reduce:transition-[opacity] motion-reduce:duration-150">
<div class="px-[26px] pt-[26px] pb-[22px]">
<span aria-hidden="true" class="inline-grid place-items-center size-12 mb-4 rounded-full bg-tw01-tint text-tw01-danger">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M6 6l1 14a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2l1-14"/></svg>
</span>
<h2 id="tw-01-title" class="mb-2 text-[1.15rem] font-bold tracking-[-0.01em] leading-tight text-tw01-ink">Delete this report?</h2>
<p id="tw-01-desc" class="mb-[22px] text-sm leading-relaxed text-tw01-sub text-pretty">You're about to permanently delete <strong class="font-semibold text-tw01-ink">Q3 Financial Report.pdf</strong>. This action cannot be undone and any team members sharing this report will lose access immediately.</p>
<div class="grid grid-cols-2 gap-2">
<button type="button" autofocus data-tw01-close class="min-h-11 rounded-lg border-0 text-sm font-semibold text-tw01-ink bg-tw01-well cursor-pointer transition-[background-color,translate] duration-150 hover:bg-tw01-wellhover active:translate-y-px focus-visible:outline-2 focus-visible:outline-tw01-ink focus-visible:outline-offset-2 motion-reduce:active:translate-y-0">Cancel</button>
<button type="button" data-tw01-close class="min-h-11 rounded-lg border-0 text-sm font-semibold text-white bg-tw01-danger cursor-pointer transition-[background-color,translate] duration-150 hover:bg-tw01-dangerhover active:translate-y-px focus-visible:outline-2 focus-visible:outline-tw01-danger focus-visible:outline-offset-2 motion-reduce:active:translate-y-0">Delete report</button>
</div>
</div>
</dialog>
</section><section class="tw-01 w-full min-h-svh grid place-items-center bg-tw01-bg font-tw01-sans px-6 py-10">
<button type="button" data-tw01-open class="inline-flex items-center justify-center min-h-11 px-6 rounded-lg text-[15px] font-semibold text-tw01-danger bg-transparent border border-tw01-danger cursor-pointer transition-colors duration-200 hover:bg-tw01-danger hover:text-white focus-visible:outline-2 focus-visible:outline-tw01-danger focus-visible:outline-offset-2">Delete report</button>
<dialog id="tw-01-dlg" aria-labelledby="tw-01-title" aria-describedby="tw-01-desc" class="m-auto w-[min(360px,94vw)] p-0 border-0 rounded-[14px] text-center text-tw01-ink bg-tw01-card shadow-[0_30px_60px_-20px_#00000066] origin-bottom-right opacity-0 transition-[opacity,translate,rotate,scale,overlay,display] duration-[400ms] ease-[var(--ease-tw01-spring)] transition-discrete open:opacity-100 open:translate-x-0 open:translate-y-0 open:rotate-0 open:scale-100 open:starting:opacity-0 open:starting:translate-x-6 open:starting:translate-y-8 open:starting:rotate-3 open:starting:scale-[0.96] open:backdrop:bg-tw01-scrim open:backdrop:backdrop-blur-[4px] motion-reduce:transition-[opacity] motion-reduce:duration-150">
<div class="px-[26px] pt-[26px] pb-[22px]">
<span aria-hidden="true" class="inline-grid place-items-center size-12 mb-4 rounded-full bg-tw01-tint text-tw01-danger">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M6 6l1 14a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2l1-14"/></svg>
</span>
<h2 id="tw-01-title" class="mb-2 text-[1.15rem] font-bold tracking-[-0.01em] leading-tight text-tw01-ink">Delete this report?</h2>
<p id="tw-01-desc" class="mb-[22px] text-sm leading-relaxed text-tw01-sub text-pretty">You're about to permanently delete <strong class="font-semibold text-tw01-ink">Q3 Financial Report.pdf</strong>. This action cannot be undone and any team members sharing this report will lose access immediately.</p>
<div class="grid grid-cols-2 gap-2">
<button type="button" autofocus data-tw01-close class="min-h-11 rounded-lg border-0 text-sm font-semibold text-tw01-ink bg-tw01-well cursor-pointer transition-[background-color,translate] duration-150 hover:bg-tw01-wellhover active:translate-y-px focus-visible:outline-2 focus-visible:outline-tw01-ink focus-visible:outline-offset-2 motion-reduce:active:translate-y-0">Cancel</button>
<button type="button" data-tw01-close class="min-h-11 rounded-lg border-0 text-sm font-semibold text-white bg-tw01-danger cursor-pointer transition-[background-color,translate] duration-150 hover:bg-tw01-dangerhover active:translate-y-px focus-visible:outline-2 focus-visible:outline-tw01-danger focus-visible:outline-offset-2 motion-reduce:active:translate-y-0">Delete report</button>
</div>
</div>
</dialog>
</section>@import "tailwindcss";
@theme {
--color-tw01-bg: #f8fafc;
--color-tw01-bg: oklch(0.98 0.005 250);
--color-tw01-card: #ffffff;
--color-tw01-card: oklch(1 0 0);
--color-tw01-well: #f1f5f9;
--color-tw01-well: oklch(0.96 0.006 250);
--color-tw01-wellhover: #e2e8f0;
--color-tw01-wellhover: oklch(0.92 0.01 250);
--color-tw01-ink: #0f172a;
--color-tw01-ink: oklch(0.23 0.04 265);
--color-tw01-sub: #64748b;
--color-tw01-sub: oklch(0.55 0.03 255);
--color-tw01-danger: #dc2626;
--color-tw01-danger: oklch(0.58 0.21 27);
--color-tw01-dangerhover: #b91c1c;
--color-tw01-dangerhover: oklch(0.51 0.19 27);
--color-tw01-tint: #dc262624;
--color-tw01-tint: oklch(0.58 0.21 27 / 0.14);
--color-tw01-scrim: #0f172ab8;
--color-tw01-scrim: oklch(0.23 0.04 265 / 0.72);
--font-tw01-sans: 'Outfit', system-ui, sans-serif;
--ease-tw01-spring: cubic-bezier(0.34, 1.36, 0.44, 1);
}
/* Root rule: the width half of the full-bleed contract; the height comes from the
markup's min-h-svh utility. */
.tw-01 {
width: 100%;
}
/* The one thing the variants cannot hold: the ::backdrop enter/exit transition. */
#tw-01-dlg::backdrop {
background: oklch(0.23 0.04 265 / 0);
backdrop-filter: blur(0px);
transition: background .22s ease, backdrop-filter .22s ease, overlay .22s allow-discrete, display .22s allow-discrete;
}
@starting-style {
#tw-01-dlg[open]::backdrop {
background: oklch(0.23 0.04 265 / 0);
backdrop-filter: blur(0px);
}
}
@media (prefers-reduced-motion: reduce) {
#tw-01-dlg::backdrop {
transition-duration: .001s;
}
}@import "tailwindcss";
@theme {
--color-tw01-bg: #f8fafc;
--color-tw01-bg: oklch(0.98 0.005 250);
--color-tw01-card: #ffffff;
--color-tw01-card: oklch(1 0 0);
--color-tw01-well: #f1f5f9;
--color-tw01-well: oklch(0.96 0.006 250);
--color-tw01-wellhover: #e2e8f0;
--color-tw01-wellhover: oklch(0.92 0.01 250);
--color-tw01-ink: #0f172a;
--color-tw01-ink: oklch(0.23 0.04 265);
--color-tw01-sub: #64748b;
--color-tw01-sub: oklch(0.55 0.03 255);
--color-tw01-danger: #dc2626;
--color-tw01-danger: oklch(0.58 0.21 27);
--color-tw01-dangerhover: #b91c1c;
--color-tw01-dangerhover: oklch(0.51 0.19 27);
--color-tw01-tint: #dc262624;
--color-tw01-tint: oklch(0.58 0.21 27 / 0.14);
--color-tw01-scrim: #0f172ab8;
--color-tw01-scrim: oklch(0.23 0.04 265 / 0.72);
--font-tw01-sans: 'Outfit', system-ui, sans-serif;
--ease-tw01-spring: cubic-bezier(0.34, 1.36, 0.44, 1);
}
/* Root rule: the width half of the full-bleed contract; the height comes from the
markup's min-h-svh utility. */
.tw-01 {
width: 100%;
}
/* The one thing the variants cannot hold: the ::backdrop enter/exit transition. */
#tw-01-dlg::backdrop {
background: oklch(0.23 0.04 265 / 0);
backdrop-filter: blur(0px);
transition: background .22s ease, backdrop-filter .22s ease, overlay .22s allow-discrete, display .22s allow-discrete;
}
@starting-style {
#tw-01-dlg[open]::backdrop {
background: oklch(0.23 0.04 265 / 0);
backdrop-filter: blur(0px);
}
}
@media (prefers-reduced-motion: reduce) {
#tw-01-dlg::backdrop {
transition-duration: .001s;
}
}(function () {
var root = document.querySelector('.tw-01');
var dlg = root.querySelector('#tw-01-dlg');
var card = dlg.firstElementChild;
root.querySelector('[data-tw01-open]').addEventListener('click', function () { dlg.showModal(); });
root.querySelectorAll('[data-tw01-close]').forEach(function (b) {
b.addEventListener('click', function () { dlg.close(); });
});
// Light-dismiss: the click landed on the dialog box, outside the card's rect.
dlg.addEventListener('click', function (e) {
var r = card.getBoundingClientRect();
if (e.clientX < r.left || e.clientX > r.right || e.clientY < r.top || e.clientY > r.bottom) dlg.close();
});
})();(function () {
var root = document.querySelector('.tw-01');
var dlg = root.querySelector('#tw-01-dlg');
var card = dlg.firstElementChild;
root.querySelector('[data-tw01-open]').addEventListener('click', function () { dlg.showModal(); });
root.querySelectorAll('[data-tw01-close]').forEach(function (b) {
b.addEventListener('click', function () { dlg.close(); });
});
// Light-dismiss: the click landed on the dialog box, outside the card's rect.
dlg.addEventListener('click', function (e) {
var r = card.getBoundingClientRect();
if (e.clientX < r.left || e.clientX > r.right || e.clientY < r.top || e.clientY > r.bottom) dlg.close();
});
})();Setup — do this once per project
Skip this if your project already runs Tailwind v4.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>Compiles in the browser — perfect for a CodePen or a quick test, not for production.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@import "tailwindcss";
@theme {
--color-tw01-bg: #f8fafc;
--color-tw01-bg: oklch(0.98 0.005 250);
--color-tw01-card: #ffffff;
--color-tw01-card: oklch(1 0 0);
--color-tw01-well: #f1f5f9;
--color-tw01-well: oklch(0.96 0.006 250);
--color-tw01-wellhover: #e2e8f0;
--color-tw01-wellhover: oklch(0.92 0.01 250);
--color-tw01-ink: #0f172a;
--color-tw01-ink: oklch(0.23 0.04 265);
--color-tw01-sub: #64748b;
--color-tw01-sub: oklch(0.55 0.03 255);
--color-tw01-danger: #dc2626;
--color-tw01-danger: oklch(0.58 0.21 27);
--color-tw01-dangerhover: #b91c1c;
--color-tw01-dangerhover: oklch(0.51 0.19 27);
--color-tw01-tint: #dc262624;
--color-tw01-tint: oklch(0.58 0.21 27 / 0.14);
--color-tw01-scrim: #0f172ab8;
--color-tw01-scrim: oklch(0.23 0.04 265 / 0.72);
--font-tw01-sans: 'Outfit', system-ui, sans-serif;
--ease-tw01-spring: cubic-bezier(0.34, 1.36, 0.44, 1);
}
/* Root rule: the width half of the full-bleed contract; the height comes from the
markup's min-h-svh utility. */
.tw-01 {
width: 100%;
}
/* The one thing the variants cannot hold: the ::backdrop enter/exit transition. */
#tw-01-dlg::backdrop {
background: oklch(0.23 0.04 265 / 0);
backdrop-filter: blur(0px);
transition: background .22s ease, backdrop-filter .22s ease, overlay .22s allow-discrete, display .22s allow-discrete;
}
@starting-style {
#tw-01-dlg[open]::backdrop {
background: oklch(0.23 0.04 265 / 0);
backdrop-filter: blur(0px);
}
}
@media (prefers-reduced-motion: reduce) {
#tw-01-dlg::backdrop {
transition-duration: .001s;
}
}@import "tailwindcss";
@theme {
--color-tw01-bg: #f8fafc;
--color-tw01-bg: oklch(0.98 0.005 250);
--color-tw01-card: #ffffff;
--color-tw01-card: oklch(1 0 0);
--color-tw01-well: #f1f5f9;
--color-tw01-well: oklch(0.96 0.006 250);
--color-tw01-wellhover: #e2e8f0;
--color-tw01-wellhover: oklch(0.92 0.01 250);
--color-tw01-ink: #0f172a;
--color-tw01-ink: oklch(0.23 0.04 265);
--color-tw01-sub: #64748b;
--color-tw01-sub: oklch(0.55 0.03 255);
--color-tw01-danger: #dc2626;
--color-tw01-danger: oklch(0.58 0.21 27);
--color-tw01-dangerhover: #b91c1c;
--color-tw01-dangerhover: oklch(0.51 0.19 27);
--color-tw01-tint: #dc262624;
--color-tw01-tint: oklch(0.58 0.21 27 / 0.14);
--color-tw01-scrim: #0f172ab8;
--color-tw01-scrim: oklch(0.23 0.04 265 / 0.72);
--font-tw01-sans: 'Outfit', system-ui, sans-serif;
--ease-tw01-spring: cubic-bezier(0.34, 1.36, 0.44, 1);
}
/* Root rule: the width half of the full-bleed contract; the height comes from the
markup's min-h-svh utility. */
.tw-01 {
width: 100%;
}
/* The one thing the variants cannot hold: the ::backdrop enter/exit transition. */
#tw-01-dlg::backdrop {
background: oklch(0.23 0.04 265 / 0);
backdrop-filter: blur(0px);
transition: background .22s ease, backdrop-filter .22s ease, overlay .22s allow-discrete, display .22s allow-discrete;
}
@starting-style {
#tw-01-dlg[open]::backdrop {
background: oklch(0.23 0.04 265 / 0);
backdrop-filter: blur(0px);
}
}
@media (prefers-reduced-motion: reduce) {
#tw-01-dlg::backdrop {
transition-duration: .001s;
}
}<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700;800&display=swap">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700;800&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- The original's
@keyframes md-01-inis gone: a one-shot entrance with a known start and end is a transition, and stating it asopen:starting:puts the start frame beside the end frame instead of in a keyframe block fifty lines away. - The spring curve is a
@theme--ease-*token, soease-[var(--ease-tw01-spring)]is the same curve everywhere and retunes from one line. origin-bottom-rightmatters as much as the rotation — pivoting at the centre turns a corner swing into a wobble.- Behaviour change from the CSS original: a real
<dialog>withshowModal()replaces the.is-opendiv, so the focus trap, Esc, background inerting, scroll lock and focus restore are the browser's and the controller drops from ~25 lines to 10. - UI change: both actions are
min-h-11(44px) instead of 10px padding, and the pair is agrid grid-cols-2 gap-2so they stay equal-width at every string length.
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 Confirm Delete Modal with Corner Slide-and-Spin Entrance
Source: https://codefronts.com/components/tailwind-css-modals/tailwind-confirm-delete-modal-corner-slide-spin/
A destructive-action confirmation whose card swings in from the bottom-right at 3° and settles square. The port keeps the muted intensity of the original — a spring easing token, not a bounce — and states the whole entrance as open: plus open:starting: on the dialog, so the closed state, the open state and the pre-transition frame are one class list.
## HTML
```html
<section class="tw-01 w-full min-h-svh grid place-items-center bg-tw01-bg font-tw01-sans px-6 py-10">
<button type="button" data-tw01-open class="inline-flex items-center justify-center min-h-11 px-6 rounded-lg text-[15px] font-semibold text-tw01-danger bg-transparent border border-tw01-danger cursor-pointer transition-colors duration-200 hover:bg-tw01-danger hover:text-white focus-visible:outline-2 focus-visible:outline-tw01-danger focus-visible:outline-offset-2">Delete report</button>
<dialog id="tw-01-dlg" aria-labelledby="tw-01-title" aria-describedby="tw-01-desc" class="m-auto w-[min(360px,94vw)] p-0 border-0 rounded-[14px] text-center text-tw01-ink bg-tw01-card shadow-[0_30px_60px_-20px_#00000066] origin-bottom-right opacity-0 transition-[opacity,translate,rotate,scale,overlay,display] duration-[400ms] ease-[var(--ease-tw01-spring)] transition-discrete open:opacity-100 open:translate-x-0 open:translate-y-0 open:rotate-0 open:scale-100 open:starting:opacity-0 open:starting:translate-x-6 open:starting:translate-y-8 open:starting:rotate-3 open:starting:scale-[0.96] open:backdrop:bg-tw01-scrim open:backdrop:backdrop-blur-[4px] motion-reduce:transition-[opacity] motion-reduce:duration-150">
<div class="px-[26px] pt-[26px] pb-[22px]">
<span aria-hidden="true" class="inline-grid place-items-center size-12 mb-4 rounded-full bg-tw01-tint text-tw01-danger">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M6 6l1 14a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2l1-14"/></svg>
</span>
<h2 id="tw-01-title" class="mb-2 text-[1.15rem] font-bold tracking-[-0.01em] leading-tight text-tw01-ink">Delete this report?</h2>
<p id="tw-01-desc" class="mb-[22px] text-sm leading-relaxed text-tw01-sub text-pretty">You're about to permanently delete <strong class="font-semibold text-tw01-ink">Q3 Financial Report.pdf</strong>. This action cannot be undone and any team members sharing this report will lose access immediately.</p>
<div class="grid grid-cols-2 gap-2">
<button type="button" autofocus data-tw01-close class="min-h-11 rounded-lg border-0 text-sm font-semibold text-tw01-ink bg-tw01-well cursor-pointer transition-[background-color,translate] duration-150 hover:bg-tw01-wellhover active:translate-y-px focus-visible:outline-2 focus-visible:outline-tw01-ink focus-visible:outline-offset-2 motion-reduce:active:translate-y-0">Cancel</button>
<button type="button" data-tw01-close class="min-h-11 rounded-lg border-0 text-sm font-semibold text-white bg-tw01-danger cursor-pointer transition-[background-color,translate] duration-150 hover:bg-tw01-dangerhover active:translate-y-px focus-visible:outline-2 focus-visible:outline-tw01-danger focus-visible:outline-offset-2 motion-reduce:active:translate-y-0">Delete report</button>
</div>
</div>
</dialog>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tw01-bg: #f8fafc;
--color-tw01-bg: oklch(0.98 0.005 250);
--color-tw01-card: #ffffff;
--color-tw01-card: oklch(1 0 0);
--color-tw01-well: #f1f5f9;
--color-tw01-well: oklch(0.96 0.006 250);
--color-tw01-wellhover: #e2e8f0;
--color-tw01-wellhover: oklch(0.92 0.01 250);
--color-tw01-ink: #0f172a;
--color-tw01-ink: oklch(0.23 0.04 265);
--color-tw01-sub: #64748b;
--color-tw01-sub: oklch(0.55 0.03 255);
--color-tw01-danger: #dc2626;
--color-tw01-danger: oklch(0.58 0.21 27);
--color-tw01-dangerhover: #b91c1c;
--color-tw01-dangerhover: oklch(0.51 0.19 27);
--color-tw01-tint: #dc262624;
--color-tw01-tint: oklch(0.58 0.21 27 / 0.14);
--color-tw01-scrim: #0f172ab8;
--color-tw01-scrim: oklch(0.23 0.04 265 / 0.72);
--font-tw01-sans: 'Outfit', system-ui, sans-serif;
--ease-tw01-spring: cubic-bezier(0.34, 1.36, 0.44, 1);
}
/* Root rule: the width half of the full-bleed contract; the height comes from the
markup's min-h-svh utility. */
.tw-01 {
width: 100%;
}
/* The one thing the variants cannot hold: the ::backdrop enter/exit transition. */
#tw-01-dlg::backdrop {
background: oklch(0.23 0.04 265 / 0);
backdrop-filter: blur(0px);
transition: background .22s ease, backdrop-filter .22s ease, overlay .22s allow-discrete, display .22s allow-discrete;
}
@starting-style {
#tw-01-dlg[open]::backdrop {
background: oklch(0.23 0.04 265 / 0);
backdrop-filter: blur(0px);
}
}
@media (prefers-reduced-motion: reduce) {
#tw-01-dlg::backdrop {
transition-duration: .001s;
}
}
```
## JavaScript
```js
(function () {
var root = document.querySelector('.tw-01');
var dlg = root.querySelector('#tw-01-dlg');
var card = dlg.firstElementChild;
root.querySelector('[data-tw01-open]').addEventListener('click', function () { dlg.showModal(); });
root.querySelectorAll('[data-tw01-close]').forEach(function (b) {
b.addEventListener('click', function () { dlg.close(); });
});
// Light-dismiss: the click landed on the dialog box, outside the card's rect.
dlg.addEventListener('click', function (e) {
var r = card.getBoundingClientRect();
if (e.clientX < r.left || e.clientX > r.right || e.clientY < r.top || e.clientY > r.bottom) dlg.close();
});
})();
```Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Confirm Delete Modal with Corner Slide-and-Spin Entrance
Source: https://codefronts.com/components/tailwind-css-modals/tailwind-confirm-delete-modal-corner-slide-spin/
A destructive-action confirmation whose card swings in from the bottom-right at 3° and settles square. The port keeps the muted intensity of the original — a spring easing token, not a bounce — and states the whole entrance as open: plus open:starting: on the dialog, so the closed state, the open state and the pre-transition frame are one class list.
## HTML
```html
<section class="tw-01 w-full min-h-svh grid place-items-center bg-tw01-bg font-tw01-sans px-6 py-10">
<button type="button" data-tw01-open class="inline-flex items-center justify-center min-h-11 px-6 rounded-lg text-[15px] font-semibold text-tw01-danger bg-transparent border border-tw01-danger cursor-pointer transition-colors duration-200 hover:bg-tw01-danger hover:text-white focus-visible:outline-2 focus-visible:outline-tw01-danger focus-visible:outline-offset-2">Delete report</button>
<dialog id="tw-01-dlg" aria-labelledby="tw-01-title" aria-describedby="tw-01-desc" class="m-auto w-[min(360px,94vw)] p-0 border-0 rounded-[14px] text-center text-tw01-ink bg-tw01-card shadow-[0_30px_60px_-20px_#00000066] origin-bottom-right opacity-0 transition-[opacity,translate,rotate,scale,overlay,display] duration-[400ms] ease-[var(--ease-tw01-spring)] transition-discrete open:opacity-100 open:translate-x-0 open:translate-y-0 open:rotate-0 open:scale-100 open:starting:opacity-0 open:starting:translate-x-6 open:starting:translate-y-8 open:starting:rotate-3 open:starting:scale-[0.96] open:backdrop:bg-tw01-scrim open:backdrop:backdrop-blur-[4px] motion-reduce:transition-[opacity] motion-reduce:duration-150">
<div class="px-[26px] pt-[26px] pb-[22px]">
<span aria-hidden="true" class="inline-grid place-items-center size-12 mb-4 rounded-full bg-tw01-tint text-tw01-danger">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M6 6l1 14a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2l1-14"/></svg>
</span>
<h2 id="tw-01-title" class="mb-2 text-[1.15rem] font-bold tracking-[-0.01em] leading-tight text-tw01-ink">Delete this report?</h2>
<p id="tw-01-desc" class="mb-[22px] text-sm leading-relaxed text-tw01-sub text-pretty">You're about to permanently delete <strong class="font-semibold text-tw01-ink">Q3 Financial Report.pdf</strong>. This action cannot be undone and any team members sharing this report will lose access immediately.</p>
<div class="grid grid-cols-2 gap-2">
<button type="button" autofocus data-tw01-close class="min-h-11 rounded-lg border-0 text-sm font-semibold text-tw01-ink bg-tw01-well cursor-pointer transition-[background-color,translate] duration-150 hover:bg-tw01-wellhover active:translate-y-px focus-visible:outline-2 focus-visible:outline-tw01-ink focus-visible:outline-offset-2 motion-reduce:active:translate-y-0">Cancel</button>
<button type="button" data-tw01-close class="min-h-11 rounded-lg border-0 text-sm font-semibold text-white bg-tw01-danger cursor-pointer transition-[background-color,translate] duration-150 hover:bg-tw01-dangerhover active:translate-y-px focus-visible:outline-2 focus-visible:outline-tw01-danger focus-visible:outline-offset-2 motion-reduce:active:translate-y-0">Delete report</button>
</div>
</div>
</dialog>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tw01-bg: #f8fafc;
--color-tw01-bg: oklch(0.98 0.005 250);
--color-tw01-card: #ffffff;
--color-tw01-card: oklch(1 0 0);
--color-tw01-well: #f1f5f9;
--color-tw01-well: oklch(0.96 0.006 250);
--color-tw01-wellhover: #e2e8f0;
--color-tw01-wellhover: oklch(0.92 0.01 250);
--color-tw01-ink: #0f172a;
--color-tw01-ink: oklch(0.23 0.04 265);
--color-tw01-sub: #64748b;
--color-tw01-sub: oklch(0.55 0.03 255);
--color-tw01-danger: #dc2626;
--color-tw01-danger: oklch(0.58 0.21 27);
--color-tw01-dangerhover: #b91c1c;
--color-tw01-dangerhover: oklch(0.51 0.19 27);
--color-tw01-tint: #dc262624;
--color-tw01-tint: oklch(0.58 0.21 27 / 0.14);
--color-tw01-scrim: #0f172ab8;
--color-tw01-scrim: oklch(0.23 0.04 265 / 0.72);
--font-tw01-sans: 'Outfit', system-ui, sans-serif;
--ease-tw01-spring: cubic-bezier(0.34, 1.36, 0.44, 1);
}
/* Root rule: the width half of the full-bleed contract; the height comes from the
markup's min-h-svh utility. */
.tw-01 {
width: 100%;
}
/* The one thing the variants cannot hold: the ::backdrop enter/exit transition. */
#tw-01-dlg::backdrop {
background: oklch(0.23 0.04 265 / 0);
backdrop-filter: blur(0px);
transition: background .22s ease, backdrop-filter .22s ease, overlay .22s allow-discrete, display .22s allow-discrete;
}
@starting-style {
#tw-01-dlg[open]::backdrop {
background: oklch(0.23 0.04 265 / 0);
backdrop-filter: blur(0px);
}
}
@media (prefers-reduced-motion: reduce) {
#tw-01-dlg::backdrop {
transition-duration: .001s;
}
}
```
## JavaScript
```js
(function () {
var root = document.querySelector('.tw-01');
var dlg = root.querySelector('#tw-01-dlg');
var card = dlg.firstElementChild;
root.querySelector('[data-tw01-open]').addEventListener('click', function () { dlg.showModal(); });
root.querySelectorAll('[data-tw01-close]').forEach(function (b) {
b.addEventListener('click', function () { dlg.close(); });
});
// Light-dismiss: the click landed on the dialog box, outside the card's rect.
dlg.addEventListener('click', function (e) {
var r = card.getBoundingClientRect();
if (e.clientX < r.left || e.clientX > r.right || e.clientY < r.top || e.clientY > r.bottom) dlg.close();
});
})();
```How this works
The entrance is three variants on one element. The card rests at opacity-0; open: sets the settled state; open:starting: supplies the corner offset, the 3° tilt and the 0.96 scale the browser paints for one frame before transitioning. transition-discrete is what lets the dialog animate out as well as in.
v4 emits translate and rotate as their own properties, so the transition list names them individually — transition-[opacity,translate,rotate,scale,overlay,display]. A transform entry here would be dead code and the card would pop.
Focus starts on Cancel. autofocus sits on the ghost button, never the red one: an Enter reflex must not delete. The browser's showModal() supplies the trap, Esc, the inert background and focus restore that the original had to hand-roll.
Make it yours
- Sharpen or soften the arrival by editing
--ease-tw01-springin one place —1.56for more overshoot,1.15for a gentle settle. - Flip the corner: swap
open:starting:translate-x-6for-translate-x-6androtate-3for-rotate-3, then moveorigin-bottom-righttoorigin-bottom-left. - Retheme from
--color-tw01-danger— the icon well, the ring and the confirm button all read it. - For irreversible actions add a type-to-confirm field and gate the button with
disabled:utilities.
Gotchas — read before shipping
- Focus must start on the safe action.
autofocuson the destructive button turns a reflexive Enter into data loss — the single most common failure in delete dialogs. - Name the thing being deleted in the copy (
<strong>Q3 Financial Report.pdf</strong>). A generic "Delete this item?" produces confident wrong confirmations. transition-discreteis load-bearing: without itdisplayflips instantly and the exit never renders, however manyduration-*utilities are present.- Keep the rotation small. The original is explicit that 10°+ spins read as frivolous on a destructive prompt and measurably reduce perceived trust.
- The
::backdropfade keeps its@starting-stylein the stylesheet — nestingopen:starting:backdrop:around a pseudo-element the browser only creates on open does not emit in a stable order.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ · 117+ anim | 16.4+ · 17.5+ anim | 128+ · 129+ anim | 111+ · 117+ anim |
<dialog>/showModal() is Baseline; @starting-style and transition-behavior are the newer gate and degrade to an instant show, which never blocks the flow.