36 Tailwind CSS Modals
19 / 36

All 36 in 36 Tailwind CSS Modals

  1. 01Tailwind Login / Signup Modal with Native Dialog & Starting-Style
  2. 02Tailwind Password Reset Modal with In-Place Success State
  3. 03Tailwind Age Verification Gate with :has() and Zero JavaScript
  4. 04Tailwind Paywall Modal with Masked Article Fade
  5. 05Tailwind Product Quick-View Modal with has-checked Variant Chips
  6. 06Tailwind Shopping Cart Slide-Over Drawer with Dialog
  7. 07Tailwind Timed Newsletter Promo Popup with Frequency Cap
  8. 08Tailwind Size Guide Overlay with Zero-JS cm/inch Toggle
  9. 09Tailwind Zero-JS Lightbox Gallery with the target Variant
  10. 10Tailwind Video Trailer Modal with Aspect-Locked Iframe
  11. 11Tailwind Destructive Delete Modal with Type-to-Confirm Gate
  12. 12Tailwind Success / Error Result Modal with Drawn SVG Icon
  13. 13Tailwind GDPR Cookie Consent Banner with details Disclosure
  14. 14Tailwind Multi-Step Onboarding Modal with data-step Variants
  15. 15Tailwind Filter & Sort Drawer that Becomes a Bottom Sheet
  16. 16Tailwind Contact Support Modal with Live Validation
  17. 17Tailwind Tabbed Settings Modal with Zero-JS Radio Tabs
  18. 18Tailwind Command Palette Modal with ⌘K and Roving Selection
  19. 19Tailwind Confirm Delete Modal with Corner Slide-and-Spin Entrance
  20. 20Tailwind Login Modal with Staggered Blur-In Content Drop
  21. 21Tailwind Signup Modal with 3D Origami Fan Deploy Panels
  22. 22Tailwind Payment Checkout Modal with 3D Paper Fold Entrance
  23. 23Tailwind HIPAA Patient Consent Modal with Scroll-Gated Agreement
  24. 24Tailwind Age Verification Modal with Clip-Path Iris Aperture
  25. 25Tailwind GDPR Cookie Preferences Modal with Unfolding Category Rows
  26. 26Tailwind Video Lightbox Modal with Chromatic Glitch Materialize
  27. 27Tailwind Image Gallery Lightbox with Clip-Path Diamond Reveal
  28. 28Tailwind Add to Cart Confirmation with SVG Liquid Morph Entrance
  29. 29Tailwind Product Quick View Modal with Elastic Button Morph
  30. 30Tailwind Newsletter Signup Modal with Depth-Layer Parallax Entrance
  31. 31Tailwind Exit-Intent Discount Modal with Live Countdown and Coupon Copy
  32. 32Tailwind Restaurant Reservation Modal with Cascading Fan Panels
  33. 33Tailwind Order Success Modal with CSS-Only Confetti Burst
  34. 34Tailwind SaaS Onboarding Welcome Modal with Cascading Blur-In
  35. 35Tailwind Terms of Service Modal with Letter-Slide and Consent Gate
  36. 36Tailwind Password Reset Modal with Enumeration-Safe Confirmation
Tailwind + JSMIT licensed

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.

Live Demo
Plain CSSTailwind v4
Try it

Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time

Untitled UI React · Affiliate link

Get Untitled UI Pro

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>
@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();
  });
})();

Setup — do this once per project

Skip this if your project already runs Tailwind v4.

1Load Tailwind
<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.

2Add the stylesheetRequiredthe @theme block is what makes the custom utilities exist
@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;
  }
}
3Fonts used by this demooptional — skip if you have your own type stack
<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-in is gone: a one-shot entrance with a known start and end is a transition, and stating it as open: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, so ease-[var(--ease-tw01-spring)] is the same curve everywhere and retunes from one line.
  • origin-bottom-right matters 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> with showModal() replaces the .is-open div, 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 a grid grid-cols-2 gap-2 so they stay equal-width at every string length.
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
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-spring in one place — 1.56 for more overshoot, 1.15 for a gentle settle.
  • Flip the corner: swap open:starting:translate-x-6 for -translate-x-6 and rotate-3 for -rotate-3, then move origin-bottom-right to origin-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. autofocus on 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-discrete is load-bearing: without it display flips instantly and the exit never renders, however many duration-* 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 ::backdrop fade keeps its @starting-style in the stylesheet — nesting open:starting:backdrop: around a pseudo-element the browser only creates on open does not emit in a stable order.

Browser support

ChromeSafariFirefoxEdge
111+ · 117+ anim16.4+ · 17.5+ anim128+ · 129+ anim111+ · 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.

Techniques used in this demo

Utilitiesfirst-class utility or variant

Theme tokensset in @theme, used by a utility

HTML & JSno Tailwind involved

Search CodeFronts

Loading…