36 Tailwind CSS Modals
22 / 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 Payment Checkout Modal with 3D Paper Fold Entrance

A card-entry checkout that unfolds from the top like an envelope — rotate-x-[-70deg] to flat, ease-out, no overshoot, because payment wants precision rather than bounce. The port states the fold as open:starting: on the dialog inside a perspective-* stage and keeps the original's abandonment guard on Esc.

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-04 w-full min-h-svh grid place-items-center bg-tw04-bg font-tw04-sans px-6 py-10">
  <button type="button" data-tw04-open class="inline-flex items-center justify-center min-h-11 px-7 rounded-lg border-0 text-[15px] font-bold text-white bg-tw04-brand tabular-nums shadow-[0_8px_20px_-6px_var(--color-tw04-glow)] cursor-pointer transition-[background-color,translate] duration-200 hover:bg-tw04-brandhover hover:-translate-y-px focus-visible:outline-2 focus-visible:outline-tw04-brand focus-visible:outline-offset-2 motion-reduce:hover:translate-y-0">Complete purchase · $29.00</button>

  <dialog id="tw-04-dlg" aria-labelledby="tw-04-title" class="m-auto w-[min(460px,94vw)] p-0 border-0 bg-transparent perspective-[1600px] open:backdrop:bg-tw04-scrim open:backdrop:backdrop-blur-[6px]">
    <div class="relative px-8 pt-8 pb-6 rounded-xl bg-tw04-card text-tw04-ink text-left shadow-[0_40px_60px_-20px_#0a0e2759] origin-top transform-3d backface-hidden opacity-0 rotate-x-[-70deg] transition-[opacity,rotate,overlay,display] duration-500 ease-[cubic-bezier(.22,1,.36,1)] transition-discrete group-open:opacity-100 in-[dialog[open]]:opacity-100 in-[dialog[open]]:rotate-x-0 starting:in-[dialog[open]]:opacity-0 starting:in-[dialog[open]]:rotate-x-[-70deg] motion-reduce:transition-[opacity] motion-reduce:duration-150 motion-reduce:rotate-x-0">
      <button type="button" data-tw04-close aria-label="Close dialog" class="absolute top-3.5 right-3.5 grid place-items-center size-9 rounded-lg border-0 bg-transparent text-tw04-sub text-base cursor-pointer transition-colors duration-200 hover:bg-tw04-well hover:text-tw04-ink focus-visible:outline-2 focus-visible:outline-tw04-brand focus-visible:outline-offset-2">&#10005;</button>

      <p class="inline-flex items-center gap-2 mb-3.5 text-xs font-semibold tracking-wide text-tw04-sub"><span aria-hidden="true" class="text-xs">&#128274;</span>Secured by Stripe</p>
      <h2 id="tw-04-title" class="mb-[18px] text-[1.45rem] font-extrabold tracking-[-0.015em] leading-tight">Complete your purchase</h2>

      <div class="mb-5 px-[18px] py-4 rounded-[9px] border border-tw04-line bg-tw04-well">
        <div class="flex justify-between items-baseline py-[5px] text-sm text-tw04-sub"><span>Pro plan · monthly</span><strong class="font-semibold text-tw04-ink tabular-nums">$25.00</strong></div>
        <div class="flex justify-between items-baseline py-[5px] text-sm text-tw04-sub"><span>Tax (16%)</span><strong class="font-semibold text-tw04-ink tabular-nums">$4.00</strong></div>
        <div class="flex justify-between items-baseline pt-2.5 mt-[5px] border-t border-tw04-line text-[15px]"><span class="font-bold text-tw04-ink">Total</span><strong class="text-base font-extrabold text-tw04-ink tabular-nums">$29.00 USD</strong></div>
      </div>

      <form method="dialog" class="flex flex-col gap-3 mb-[18px]">
        <label class="flex flex-col gap-1.5">
          <span class="text-xs font-semibold tracking-wide text-tw04-sub">Card number</span>
          <input type="text" autofocus required inputmode="numeric" autocomplete="cc-number" placeholder="1234 1234 1234 1234" class="min-h-11 px-3.5 rounded-lg border border-tw04-line bg-tw04-card text-[15px] text-tw04-ink tabular-nums placeholder:text-tw04-sub transition-[border-color,box-shadow] duration-200 focus:outline-none focus:border-tw04-brand focus:shadow-[0_0_0_3px_var(--color-tw04-ring)]">
        </label>
        <div class="grid grid-cols-2 gap-2.5 max-[400px]:grid-cols-1">
          <label class="flex flex-col gap-1.5">
            <span class="text-xs font-semibold tracking-wide text-tw04-sub">Expiry</span>
            <input type="text" required inputmode="numeric" autocomplete="cc-exp" placeholder="MM / YY" class="min-h-11 px-3.5 rounded-lg border border-tw04-line bg-tw04-card text-[15px] text-tw04-ink tabular-nums placeholder:text-tw04-sub transition-[border-color,box-shadow] duration-200 focus:outline-none focus:border-tw04-brand focus:shadow-[0_0_0_3px_var(--color-tw04-ring)]">
          </label>
          <label class="flex flex-col gap-1.5">
            <span class="text-xs font-semibold tracking-wide text-tw04-sub">CVC</span>
            <input type="text" required inputmode="numeric" pattern="[0-9]{3,4}" autocomplete="cc-csc" placeholder="CVC" class="min-h-11 px-3.5 rounded-lg border border-tw04-line bg-tw04-card text-[15px] text-tw04-ink tabular-nums placeholder:text-tw04-sub transition-[border-color,box-shadow] duration-200 focus:outline-none focus:border-tw04-brand focus:shadow-[0_0_0_3px_var(--color-tw04-ring)]">
          </label>
        </div>
        <div class="grid grid-cols-[1fr_auto] gap-2.5 max-[400px]:grid-cols-1">
          <label class="flex flex-col gap-1.5">
            <span class="text-xs font-semibold tracking-wide text-tw04-sub">Cardholder name</span>
            <input type="text" required autocomplete="cc-name" placeholder="Alex Chen" class="min-h-11 px-3.5 rounded-lg border border-tw04-line bg-tw04-card text-[15px] text-tw04-ink placeholder:text-tw04-sub transition-[border-color,box-shadow] duration-200 focus:outline-none focus:border-tw04-brand focus:shadow-[0_0_0_3px_var(--color-tw04-ring)]">
          </label>
          <label class="flex flex-col gap-1.5">
            <span class="text-xs font-semibold tracking-wide text-tw04-sub">ZIP</span>
            <input type="text" required autocomplete="postal-code" placeholder="94103" class="min-h-11 w-[7rem] max-[400px]:w-full px-3.5 rounded-lg border border-tw04-line bg-tw04-card text-[15px] text-tw04-ink tabular-nums placeholder:text-tw04-sub transition-[border-color,box-shadow] duration-200 focus:outline-none focus:border-tw04-brand focus:shadow-[0_0_0_3px_var(--color-tw04-ring)]">
          </label>
        </div>
        <button type="submit" class="min-h-11 mt-1.5 px-5 rounded-lg border-0 bg-tw04-brand text-base font-bold text-white tabular-nums cursor-pointer transition-[background-color,translate] duration-200 hover:bg-tw04-brandhover active:translate-y-px focus-visible:outline-2 focus-visible:outline-tw04-brand focus-visible:outline-offset-[3px] motion-reduce:active:translate-y-0">Pay $29.00</button>
      </form>

      <div class="pt-4 text-center border-t border-tw04-line">
        <span aria-hidden="true" class="block mb-1.5 font-tw04-mono text-xs font-bold tracking-[0.14em] text-tw04-sub">VISA · MC · AMEX · DISC</span>
        <p class="text-xs text-tw04-sub">You'll be charged $29.00 today. Cancel anytime.</p>
      </div>
    </div>
  </dialog>
</section>
@import "tailwindcss";

@theme {
  --color-tw04-bg: #f7f8fa;
  --color-tw04-bg: oklch(0.98 0.003 255);
  --color-tw04-card: #ffffff;
  --color-tw04-card: oklch(1 0 0);
  --color-tw04-well: #fafbfc;
  --color-tw04-well: oklch(0.98 0.003 255);
  --color-tw04-ink: #0a0e27;
  --color-tw04-ink: oklch(0.19 0.05 275);
  --color-tw04-sub: #697386;
  --color-tw04-sub: oklch(0.56 0.02 265);
  --color-tw04-line: #e6e8ec;
  --color-tw04-line: oklch(0.92 0.004 265);
  --color-tw04-brand: #635bff;
  --color-tw04-brand: oklch(0.55 0.22 277);
  --color-tw04-brandhover: #4f46e5;
  --color-tw04-brandhover: oklch(0.5 0.22 277);
  --color-tw04-ring: #635bff2e;
  --color-tw04-ring: oklch(0.55 0.22 277 / 0.18);
  --color-tw04-glow: #635bff80;
  --color-tw04-glow: oklch(0.55 0.22 277 / 0.5);
  --color-tw04-scrim: #0a0e278c;
  --color-tw04-scrim: oklch(0.19 0.05 275 / 0.55);
  --font-tw04-sans: 'Outfit', system-ui, sans-serif;
  --font-tw04-mono: 'JetBrains Mono', ui-monospace, monospace;
}

.tw-04 {
  width: 100%;
}

#tw-04-dlg::backdrop {
  background: oklch(0.19 0.05 275 / 0);
  backdrop-filter: blur(0px);
  transition: background .25s ease, backdrop-filter .25s ease, overlay .25s allow-discrete, display .25s allow-discrete;
}

@starting-style {
  #tw-04-dlg[open]::backdrop {
    background: oklch(0.19 0.05 275 / 0);
    backdrop-filter: blur(0px);
  }
}

@media (prefers-reduced-motion: reduce) {
  #tw-04-dlg::backdrop {
    transition-duration: .001s;
  }
}
(function () {
  var root = document.querySelector('.tw-04');
  var dlg = root.querySelector('#tw-04-dlg');
  var card = dlg.firstElementChild;
  root.querySelector('[data-tw04-open]').addEventListener('click', function () { dlg.showModal(); });
  root.querySelector('[data-tw04-close]').addEventListener('click', function () { dlg.close(); });
  // Abandonment guard: Esc fires 'cancel', which is preventable. Ask before discarding.
  dlg.addEventListener('cancel', function (e) {
    var dirty = Array.prototype.some.call(dlg.querySelectorAll('input'), function (i) { return i.value.length > 0; });
    if (dirty && !confirm('You have unsaved payment info. Leave anyway?')) e.preventDefault();
  });
  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-tw04-bg: #f7f8fa;
  --color-tw04-bg: oklch(0.98 0.003 255);
  --color-tw04-card: #ffffff;
  --color-tw04-card: oklch(1 0 0);
  --color-tw04-well: #fafbfc;
  --color-tw04-well: oklch(0.98 0.003 255);
  --color-tw04-ink: #0a0e27;
  --color-tw04-ink: oklch(0.19 0.05 275);
  --color-tw04-sub: #697386;
  --color-tw04-sub: oklch(0.56 0.02 265);
  --color-tw04-line: #e6e8ec;
  --color-tw04-line: oklch(0.92 0.004 265);
  --color-tw04-brand: #635bff;
  --color-tw04-brand: oklch(0.55 0.22 277);
  --color-tw04-brandhover: #4f46e5;
  --color-tw04-brandhover: oklch(0.5 0.22 277);
  --color-tw04-ring: #635bff2e;
  --color-tw04-ring: oklch(0.55 0.22 277 / 0.18);
  --color-tw04-glow: #635bff80;
  --color-tw04-glow: oklch(0.55 0.22 277 / 0.5);
  --color-tw04-scrim: #0a0e278c;
  --color-tw04-scrim: oklch(0.19 0.05 275 / 0.55);
  --font-tw04-sans: 'Outfit', system-ui, sans-serif;
  --font-tw04-mono: 'JetBrains Mono', ui-monospace, monospace;
}

.tw-04 {
  width: 100%;
}

#tw-04-dlg::backdrop {
  background: oklch(0.19 0.05 275 / 0);
  backdrop-filter: blur(0px);
  transition: background .25s ease, backdrop-filter .25s ease, overlay .25s allow-discrete, display .25s allow-discrete;
}

@starting-style {
  #tw-04-dlg[open]::backdrop {
    background: oklch(0.19 0.05 275 / 0);
    backdrop-filter: blur(0px);
  }
}

@media (prefers-reduced-motion: reduce) {
  #tw-04-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&family=JetBrains+Mono:wght@500;700&display=swap">

What changed in the port

Which parts became utilities, which stayed CSS, and why.

  • The dialog holds the perspective and stays transparent; the card inside is the element that folds. Perspective and the transform it governs cannot live on the same element.
  • Because the animated element is the dialog's child rather than the dialog itself, the open state is read with in-[dialog[open]]: and its starting: twin — the same two-state idea as open:starting:, one level down.
  • The Esc guard uses the native cancel event instead of a document keydown listener: it fires only for this dialog, and preventDefault() is the supported way to refuse a dismissal.
  • tabular-nums on the summary and the pay button is the difference between a price that updates and a price that jitters.
  • UI change from the CSS original: all fields and both buttons are 44px, the ZIP field is sized to its content instead of taking half the row, and the field grid collapses to one column under 400px.
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 Payment Checkout Modal with 3D Paper Fold Entrance
Source: https://codefronts.com/components/tailwind-css-modals/tailwind-payment-checkout-modal-3d-paper-fold/

A card-entry checkout that unfolds from the top like an envelope — rotate-x-[-70deg] to flat, ease-out, no overshoot, because payment wants precision rather than bounce. The port states the fold as open:starting: on the dialog inside a perspective-* stage and keeps the original's abandonment guard on Esc.
## HTML
```html
<section class="tw-04 w-full min-h-svh grid place-items-center bg-tw04-bg font-tw04-sans px-6 py-10">
  <button type="button" data-tw04-open class="inline-flex items-center justify-center min-h-11 px-7 rounded-lg border-0 text-[15px] font-bold text-white bg-tw04-brand tabular-nums shadow-[0_8px_20px_-6px_var(--color-tw04-glow)] cursor-pointer transition-[background-color,translate] duration-200 hover:bg-tw04-brandhover hover:-translate-y-px focus-visible:outline-2 focus-visible:outline-tw04-brand focus-visible:outline-offset-2 motion-reduce:hover:translate-y-0">Complete purchase · $29.00</button>

  <dialog id="tw-04-dlg" aria-labelledby="tw-04-title" class="m-auto w-[min(460px,94vw)] p-0 border-0 bg-transparent perspective-[1600px] open:backdrop:bg-tw04-scrim open:backdrop:backdrop-blur-[6px]">
    <div class="relative px-8 pt-8 pb-6 rounded-xl bg-tw04-card text-tw04-ink text-left shadow-[0_40px_60px_-20px_#0a0e2759] origin-top transform-3d backface-hidden opacity-0 rotate-x-[-70deg] transition-[opacity,rotate,overlay,display] duration-500 ease-[cubic-bezier(.22,1,.36,1)] transition-discrete group-open:opacity-100 in-[dialog[open]]:opacity-100 in-[dialog[open]]:rotate-x-0 starting:in-[dialog[open]]:opacity-0 starting:in-[dialog[open]]:rotate-x-[-70deg] motion-reduce:transition-[opacity] motion-reduce:duration-150 motion-reduce:rotate-x-0">
      <button type="button" data-tw04-close aria-label="Close dialog" class="absolute top-3.5 right-3.5 grid place-items-center size-9 rounded-lg border-0 bg-transparent text-tw04-sub text-base cursor-pointer transition-colors duration-200 hover:bg-tw04-well hover:text-tw04-ink focus-visible:outline-2 focus-visible:outline-tw04-brand focus-visible:outline-offset-2">&#10005;</button>

      <p class="inline-flex items-center gap-2 mb-3.5 text-xs font-semibold tracking-wide text-tw04-sub"><span aria-hidden="true" class="text-xs">&#128274;</span>Secured by Stripe</p>
      <h2 id="tw-04-title" class="mb-[18px] text-[1.45rem] font-extrabold tracking-[-0.015em] leading-tight">Complete your purchase</h2>

      <div class="mb-5 px-[18px] py-4 rounded-[9px] border border-tw04-line bg-tw04-well">
        <div class="flex justify-between items-baseline py-[5px] text-sm text-tw04-sub"><span>Pro plan · monthly</span><strong class="font-semibold text-tw04-ink tabular-nums">$25.00</strong></div>
        <div class="flex justify-between items-baseline py-[5px] text-sm text-tw04-sub"><span>Tax (16%)</span><strong class="font-semibold text-tw04-ink tabular-nums">$4.00</strong></div>
        <div class="flex justify-between items-baseline pt-2.5 mt-[5px] border-t border-tw04-line text-[15px]"><span class="font-bold text-tw04-ink">Total</span><strong class="text-base font-extrabold text-tw04-ink tabular-nums">$29.00 USD</strong></div>
      </div>

      <form method="dialog" class="flex flex-col gap-3 mb-[18px]">
        <label class="flex flex-col gap-1.5">
          <span class="text-xs font-semibold tracking-wide text-tw04-sub">Card number</span>
          <input type="text" autofocus required inputmode="numeric" autocomplete="cc-number" placeholder="1234 1234 1234 1234" class="min-h-11 px-3.5 rounded-lg border border-tw04-line bg-tw04-card text-[15px] text-tw04-ink tabular-nums placeholder:text-tw04-sub transition-[border-color,box-shadow] duration-200 focus:outline-none focus:border-tw04-brand focus:shadow-[0_0_0_3px_var(--color-tw04-ring)]">
        </label>
        <div class="grid grid-cols-2 gap-2.5 max-[400px]:grid-cols-1">
          <label class="flex flex-col gap-1.5">
            <span class="text-xs font-semibold tracking-wide text-tw04-sub">Expiry</span>
            <input type="text" required inputmode="numeric" autocomplete="cc-exp" placeholder="MM / YY" class="min-h-11 px-3.5 rounded-lg border border-tw04-line bg-tw04-card text-[15px] text-tw04-ink tabular-nums placeholder:text-tw04-sub transition-[border-color,box-shadow] duration-200 focus:outline-none focus:border-tw04-brand focus:shadow-[0_0_0_3px_var(--color-tw04-ring)]">
          </label>
          <label class="flex flex-col gap-1.5">
            <span class="text-xs font-semibold tracking-wide text-tw04-sub">CVC</span>
            <input type="text" required inputmode="numeric" pattern="[0-9]{3,4}" autocomplete="cc-csc" placeholder="CVC" class="min-h-11 px-3.5 rounded-lg border border-tw04-line bg-tw04-card text-[15px] text-tw04-ink tabular-nums placeholder:text-tw04-sub transition-[border-color,box-shadow] duration-200 focus:outline-none focus:border-tw04-brand focus:shadow-[0_0_0_3px_var(--color-tw04-ring)]">
          </label>
        </div>
        <div class="grid grid-cols-[1fr_auto] gap-2.5 max-[400px]:grid-cols-1">
          <label class="flex flex-col gap-1.5">
            <span class="text-xs font-semibold tracking-wide text-tw04-sub">Cardholder name</span>
            <input type="text" required autocomplete="cc-name" placeholder="Alex Chen" class="min-h-11 px-3.5 rounded-lg border border-tw04-line bg-tw04-card text-[15px] text-tw04-ink placeholder:text-tw04-sub transition-[border-color,box-shadow] duration-200 focus:outline-none focus:border-tw04-brand focus:shadow-[0_0_0_3px_var(--color-tw04-ring)]">
          </label>
          <label class="flex flex-col gap-1.5">
            <span class="text-xs font-semibold tracking-wide text-tw04-sub">ZIP</span>
            <input type="text" required autocomplete="postal-code" placeholder="94103" class="min-h-11 w-[7rem] max-[400px]:w-full px-3.5 rounded-lg border border-tw04-line bg-tw04-card text-[15px] text-tw04-ink tabular-nums placeholder:text-tw04-sub transition-[border-color,box-shadow] duration-200 focus:outline-none focus:border-tw04-brand focus:shadow-[0_0_0_3px_var(--color-tw04-ring)]">
          </label>
        </div>
        <button type="submit" class="min-h-11 mt-1.5 px-5 rounded-lg border-0 bg-tw04-brand text-base font-bold text-white tabular-nums cursor-pointer transition-[background-color,translate] duration-200 hover:bg-tw04-brandhover active:translate-y-px focus-visible:outline-2 focus-visible:outline-tw04-brand focus-visible:outline-offset-[3px] motion-reduce:active:translate-y-0">Pay $29.00</button>
      </form>

      <div class="pt-4 text-center border-t border-tw04-line">
        <span aria-hidden="true" class="block mb-1.5 font-tw04-mono text-xs font-bold tracking-[0.14em] text-tw04-sub">VISA · MC · AMEX · DISC</span>
        <p class="text-xs text-tw04-sub">You'll be charged $29.00 today. Cancel anytime.</p>
      </div>
    </div>
  </dialog>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-tw04-bg: #f7f8fa;
  --color-tw04-bg: oklch(0.98 0.003 255);
  --color-tw04-card: #ffffff;
  --color-tw04-card: oklch(1 0 0);
  --color-tw04-well: #fafbfc;
  --color-tw04-well: oklch(0.98 0.003 255);
  --color-tw04-ink: #0a0e27;
  --color-tw04-ink: oklch(0.19 0.05 275);
  --color-tw04-sub: #697386;
  --color-tw04-sub: oklch(0.56 0.02 265);
  --color-tw04-line: #e6e8ec;
  --color-tw04-line: oklch(0.92 0.004 265);
  --color-tw04-brand: #635bff;
  --color-tw04-brand: oklch(0.55 0.22 277);
  --color-tw04-brandhover: #4f46e5;
  --color-tw04-brandhover: oklch(0.5 0.22 277);
  --color-tw04-ring: #635bff2e;
  --color-tw04-ring: oklch(0.55 0.22 277 / 0.18);
  --color-tw04-glow: #635bff80;
  --color-tw04-glow: oklch(0.55 0.22 277 / 0.5);
  --color-tw04-scrim: #0a0e278c;
  --color-tw04-scrim: oklch(0.19 0.05 275 / 0.55);
  --font-tw04-sans: 'Outfit', system-ui, sans-serif;
  --font-tw04-mono: 'JetBrains Mono', ui-monospace, monospace;
}

.tw-04 {
  width: 100%;
}

#tw-04-dlg::backdrop {
  background: oklch(0.19 0.05 275 / 0);
  backdrop-filter: blur(0px);
  transition: background .25s ease, backdrop-filter .25s ease, overlay .25s allow-discrete, display .25s allow-discrete;
}

@starting-style {
  #tw-04-dlg[open]::backdrop {
    background: oklch(0.19 0.05 275 / 0);
    backdrop-filter: blur(0px);
  }
}

@media (prefers-reduced-motion: reduce) {
  #tw-04-dlg::backdrop {
    transition-duration: .001s;
  }
}
```

## JavaScript
```js
(function () {
  var root = document.querySelector('.tw-04');
  var dlg = root.querySelector('#tw-04-dlg');
  var card = dlg.firstElementChild;
  root.querySelector('[data-tw04-open]').addEventListener('click', function () { dlg.showModal(); });
  root.querySelector('[data-tw04-close]').addEventListener('click', function () { dlg.close(); });
  // Abandonment guard: Esc fires 'cancel', which is preventable. Ask before discarding.
  dlg.addEventListener('cancel', function (e) {
    var dirty = Array.prototype.some.call(dlg.querySelectorAll('input'), function (i) { return i.value.length > 0; });
    if (dirty && !confirm('You have unsaved payment info. Leave anyway?')) e.preventDefault();
  });
  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 fold is a transition, not a keyframe. One element moves from rotate-x-[-70deg] to flat, so open: plus open:starting: express it, with transition-discrete letting it fold back out on close.

70°, not 90°, and no overshoot. The original is explicit that a payment surface must read institutional; the deep perspective-[1600px] keeps the fold subtle rather than theatrical.

Escape is guarded. The dialog's cancel event is preventable, so a half-filled form asks before discarding. Abandoning a checkout by a stray keypress is the most expensive accidental dismissal in the collection.

Make it yours

  • Soften or deepen the fold with open:starting:rotate-x-[-45deg] or [-90deg]; flatten the space with perspective-[2800px].
  • Hinge from another edge by moving origin-top to origin-bottom / origin-left.
  • Add wallet buttons above the manual form with an "or pay with card" divider — the largest single mobile-conversion win available here.
  • For subscriptions, put the next-charge date under the pay button; it measurably reduces chargebacks.

Gotchas — read before shipping

  • These inputs are a layout. Real card data must be tokenised by your processor's SDK — raw PANs touching your server is a PCI DSS violation with five-to-six-figure consequences.
  • The cc-number / cc-exp / cc-csc / cc-name / postal-code autocomplete tokens are mandatory for wallet and keychain autofill; dropping them costs a large share of mobile checkouts.
  • backface-hidden plus transform-3d together — with only one of them Safari can flash the card's reverse face mid-fold.
  • Guard Esc with e.preventDefault() inside the cancel handler; calling close() first and asking afterwards has already thrown the data away.
  • Do not client-side validate beyond Luhn and an unexpired date. AVS, CVC match and 3DS are server-side facts, and a clever front-end check just rejects valid cards.

Browser support

ChromeSafariFirefoxEdge
111+ · 117+ anim16.4+ · 17.5+ anim128+ · 129+ anim111+ · 117+ anim

CSS 3D transforms are Baseline 2015; @starting-style and transition-behavior gate only the animation and degrade to an instant show.

Techniques used in this demo

Utilitiesfirst-class utility or variant

HTML & JSno Tailwind involved

Search CodeFronts

Loading…