36 CSS Modals22 / 36

CSS + JSMIT licensed

Stripe Payment Checkout Modal

A Stripe Elements-style payment checkout modal with a signature 3D Paper Fold entrance — the modal opens like an envelope unfolding via rotateX(-70deg → 0) with perspective on the parent, giving the payment surface a distinct arrival that reads as "opening a physical checkout" rather than a generic overlay. Muted intensity (70° start instead of 90°, no overshoot) because payment forms require trust — the fold is present but subtle enough to not undermine the transactional gravity.

Published Updated

Live Demo
Try it

The code

<div class="md-04">
  <button class="md-04__trigger" id="md-04-open">Complete purchase · $29.00</button>
  <div class="md-04__backdrop" id="md-04-dialog" role="dialog" aria-modal="true" aria-labelledby="md-04-title">
    <div class="md-04__stage">
      <div class="md-04__modal">
        <button class="md-04__x md-04__close" aria-label="Close">✕</button>
        <div class="md-04__brand">
          <span class="md-04__lock" aria-hidden="true">🔒</span>
          Secured by Stripe
        </div>
        <h2 class="md-04__title" id="md-04-title">Complete your purchase</h2>
        <div class="md-04__summary">
          <div class="md-04__row"><span>Pro plan · monthly</span><strong>$25.00</strong></div>
          <div class="md-04__row"><span>Tax (16%)</span><strong>$4.00</strong></div>
          <div class="md-04__row md-04__row--total"><span>Total</span><strong>$29.00 USD</strong></div>
        </div>
        <form class="md-04__form" onsubmit="event.preventDefault()">
          <label class="md-04__label">
            <span>Card number</span>
            <input type="text" name="cc-number" autocomplete="cc-number" placeholder="1234 1234 1234 1234" inputmode="numeric" required />
          </label>
          <div class="md-04__row-fields">
            <label class="md-04__label">
              <span>Expiry</span>
              <input type="text" name="cc-exp" autocomplete="cc-exp" placeholder="MM / YY" inputmode="numeric" required />
            </label>
            <label class="md-04__label">
              <span>CVC</span>
              <input type="text" name="cc-csc" autocomplete="cc-csc" placeholder="CVC" inputmode="numeric" pattern="[0-9]{3,4}" required />
            </label>
          </div>
          <div class="md-04__row-fields">
            <label class="md-04__label md-04__label--wide">
              <span>Cardholder name</span>
              <input type="text" name="cc-name" autocomplete="cc-name" placeholder="Alex Chen" required />
            </label>
            <label class="md-04__label">
              <span>ZIP</span>
              <input type="text" name="postal-code" autocomplete="postal-code" placeholder="94103" required />
            </label>
          </div>
          <button type="submit" class="md-04__submit">Pay $29.00</button>
        </form>
        <div class="md-04__foot">
          <span class="md-04__cards" aria-hidden="true">VISA · MC · AMEX · DISC</span>
          <p>You'll be charged $29.00 today. Cancel anytime.</p>
        </div>
      </div>
    </div>
  </div>
</div>
.md-04 {
  --page-bg: #f7f8fa;
  --card: #ffffff;
  --input: #ffffff;
  --input-border: #e6e8ec;
  --ink: #0a0e27;
  --sub: #697386;
  --brand: #635bff;
  --brand-tint: rgba(99,91,255,.06);
  --line: #e6e8ec;
  font-family: -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
  min-height: 100vh;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 32px;
  background: var(--page-bg);
  color: var(--ink);
}

.md-04 *,
.md-04 *::before,
.md-04 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.md-04 ::selection {
  background: var(--brand);
  color: #fff;
}

.md-04__trigger {
  padding: 12px 26px;
  border: none;
  border-radius: 8px;
  background: var(--brand);
  color: #fff;
  font-family: inherit;
  font-size: .92rem;
  font-weight: 700;
  cursor: pointer;
  box-shadow: 0 8px 20px -6px rgba(99,91,255,.5);
  transition: transform .12s ease,background .18s ease;
}

.md-04__trigger:hover {
  background: #4f46e5;
  transform: translateY(-1px);
}

.md-04__backdrop {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(10,14,39,.55);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  opacity: 0;
  visibility: hidden;
  transition: opacity .25s ease,visibility 0s linear .25s;
}

.md-04__backdrop.is-open {
  opacity: 1;
  visibility: visible;
  transition: opacity .25s ease,visibility 0s linear 0s;
}
/* 3D stage for Paper Fold — perspective gives depth to the envelope opening */

.md-04__stage {
  perspective: 1600px;
  width: min(460px,100%);
}

.md-04__modal {
  position: relative;
  padding: 32px 32px 24px;
  border-radius: 12px;
  background: var(--card);
  box-shadow: 0 40px 60px -20px rgba(10,14,39,.35);
  /* Signature entrance: 3D Paper Fold — modal unfolds from top like envelope */
  opacity: 0;
  transform: rotateX(-70deg);
  transform-origin: top center;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  transition: opacity .3s ease .05s,transform .5s cubic-bezier(.22,1,.36,1) .05s;
}

.md-04__backdrop.is-open .md-04__modal {
  opacity: 1;
  transform: rotateX(0);
}

.md-04__x {
  position: absolute;
  top: 14px;
  right: 14px;
  width: 32px;
  height: 32px;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: var(--sub);
  font-family: inherit;
  font-size: 16px;
  cursor: pointer;
  transition: background .18s ease,color .18s ease;
}

.md-04__x:hover {
  background: #f1f5f9;
  color: var(--ink);
}

.md-04__brand {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: .72rem;
  font-weight: 600;
  color: var(--sub);
  letter-spacing: .02em;
  margin-bottom: 14px;
}

.md-04__lock {
  font-size: 12px;
}

.md-04__title {
  font-size: 1.45rem;
  font-weight: 800;
  letter-spacing: -.015em;
  line-height: 1.2;
  margin-bottom: 18px;
}

.md-04__summary {
  padding: 16px 18px;
  background: #fafbfc;
  border: 1px solid var(--line);
  border-radius: 9px;
  margin-bottom: 20px;
}

.md-04__row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding: 5px 0;
  font-size: .88rem;
  color: var(--sub);
}

.md-04__row strong {
  color: var(--ink);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.md-04__row--total {
  padding-top: 10px;
  margin-top: 5px;
  border-top: 1px solid var(--line);
  font-size: .95rem;
}

.md-04__row--total span {
  font-weight: 700;
  color: var(--ink);
}

.md-04__row--total strong {
  font-size: 1rem;
  font-weight: 800;
}

.md-04__form {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-bottom: 18px;
}

.md-04__label {
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.md-04__label span {
  font-size: .75rem;
  font-weight: 600;
  color: var(--sub);
  letter-spacing: .02em;
}

.md-04__label input {
  padding: 11px 14px;
  border: 1px solid var(--input-border);
  border-radius: 8px;
  background: var(--input);
  color: var(--ink);
  font-family: inherit;
  font-size: .92rem;
  font-variant-numeric: tabular-nums;
  transition: border-color .18s ease,box-shadow .18s ease;
}

.md-04__label input:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--brand-tint);
}

.md-04__row-fields {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}

.md-04__label--wide {
  grid-column: span 1;
}

.md-04__submit {
  padding: 13px 22px;
  border: none;
  border-radius: 8px;
  background: var(--brand);
  color: #fff;
  font-family: inherit;
  font-size: .98rem;
  font-weight: 700;
  cursor: pointer;
  font-variant-numeric: tabular-nums;
  transition: background .18s ease,transform .12s ease;
  margin-top: 6px;
}

.md-04__submit:hover {
  background: #4f46e5;
}

.md-04__submit:active {
  transform: translateY(1px);
}

.md-04__submit:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 3px;
}

.md-04__foot {
  padding-top: 16px;
  border-top: 1px solid var(--line);
  text-align: center;
}

.md-04__cards {
  display: block;
  font-family: 'JetBrains Mono',ui-monospace,monospace;
  font-size: .72rem;
  font-weight: 700;
  color: var(--sub);
  letter-spacing: .14em;
  margin-bottom: 6px;
}

.md-04__foot p {
  font-size: .72rem;
  color: var(--sub);
}

@media (prefers-reduced-motion: reduce) {
  .md-04__backdrop,
    .md-04__modal {
    transition: none!important;
  }

  .md-04__modal {
    transform: none!important;
  }
}

@media (max-width: 400px) {
  .md-04__row-fields {
    grid-template-columns: 1fr;
  }
}
(function(){
  var root = document.querySelector('.md-04');
  if (!root) return;
  var openBtn = root.querySelector('#md-04-open');
  var dialog = root.querySelector('#md-04-dialog');
  var closeBtns = root.querySelectorAll('.md-04__close');
  var lastFocus = null;

  function open() {
    lastFocus = document.activeElement;
    dialog.classList.add('is-open');
    document.documentElement.style.overflow = 'hidden';
    var firstInput = dialog.querySelector('input[autocomplete=cc-number]');
    if (firstInput) firstInput.focus();
    document.addEventListener('keydown', onKey);
  }
  function close() {
    dialog.classList.remove('is-open');
    document.documentElement.style.overflow = '';
    document.removeEventListener('keydown', onKey);
    if (lastFocus) lastFocus.focus();
  }
  function onKey(e) {
    if (e.key === 'Escape') {
      var hasInput = Array.prototype.some.call(dialog.querySelectorAll('input'), function(i){ return i.value.length > 0; });
      if (hasInput) {
        if (!confirm('You have unsaved payment info. Leave anyway?')) return;
      }
      close();
    }
  }

  openBtn.addEventListener('click', open);
  closeBtns.forEach(function(b){ b.addEventListener('click', close); });
  dialog.addEventListener('click', function(e){ if (e.target === dialog) close(); });
})();
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 Modal 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: Stripe Payment Checkout Modal
Source: https://codefronts.com/components/css-modals/stripe-payment-checkout-modal/

A Stripe Elements-style payment checkout modal with a signature 3D Paper Fold entrance — the modal opens like an envelope unfolding via rotateX(-70deg → 0) with perspective on the parent, giving the payment surface a distinct arrival that reads as "opening a physical checkout" rather than a generic overlay. Muted intensity (70° start instead of 90°, no overshoot) because payment forms require trust — the fold is present but subtle enough to not undermine the transactional gravity.
## HTML
```html
<div class="md-04">
  <button class="md-04__trigger" id="md-04-open">Complete purchase · $29.00</button>
  <div class="md-04__backdrop" id="md-04-dialog" role="dialog" aria-modal="true" aria-labelledby="md-04-title">
    <div class="md-04__stage">
      <div class="md-04__modal">
        <button class="md-04__x md-04__close" aria-label="Close">✕</button>
        <div class="md-04__brand">
          <span class="md-04__lock" aria-hidden="true">🔒</span>
          Secured by Stripe
        </div>
        <h2 class="md-04__title" id="md-04-title">Complete your purchase</h2>
        <div class="md-04__summary">
          <div class="md-04__row"><span>Pro plan · monthly</span><strong>$25.00</strong></div>
          <div class="md-04__row"><span>Tax (16%)</span><strong>$4.00</strong></div>
          <div class="md-04__row md-04__row--total"><span>Total</span><strong>$29.00 USD</strong></div>
        </div>
        <form class="md-04__form" onsubmit="event.preventDefault()">
          <label class="md-04__label">
            <span>Card number</span>
            <input type="text" name="cc-number" autocomplete="cc-number" placeholder="1234 1234 1234 1234" inputmode="numeric" required />
          </label>
          <div class="md-04__row-fields">
            <label class="md-04__label">
              <span>Expiry</span>
              <input type="text" name="cc-exp" autocomplete="cc-exp" placeholder="MM / YY" inputmode="numeric" required />
            </label>
            <label class="md-04__label">
              <span>CVC</span>
              <input type="text" name="cc-csc" autocomplete="cc-csc" placeholder="CVC" inputmode="numeric" pattern="[0-9]{3,4}" required />
            </label>
          </div>
          <div class="md-04__row-fields">
            <label class="md-04__label md-04__label--wide">
              <span>Cardholder name</span>
              <input type="text" name="cc-name" autocomplete="cc-name" placeholder="Alex Chen" required />
            </label>
            <label class="md-04__label">
              <span>ZIP</span>
              <input type="text" name="postal-code" autocomplete="postal-code" placeholder="94103" required />
            </label>
          </div>
          <button type="submit" class="md-04__submit">Pay $29.00</button>
        </form>
        <div class="md-04__foot">
          <span class="md-04__cards" aria-hidden="true">VISA · MC · AMEX · DISC</span>
          <p>You'll be charged $29.00 today. Cancel anytime.</p>
        </div>
      </div>
    </div>
  </div>
</div>
```
## CSS
```css
.md-04 {
  --page-bg: #f7f8fa;
  --card: #ffffff;
  --input: #ffffff;
  --input-border: #e6e8ec;
  --ink: #0a0e27;
  --sub: #697386;
  --brand: #635bff;
  --brand-tint: rgba(99,91,255,.06);
  --line: #e6e8ec;
  font-family: -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
  min-height: 100vh;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 32px;
  background: var(--page-bg);
  color: var(--ink);
}

.md-04 *,
.md-04 *::before,
.md-04 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.md-04 ::selection {
  background: var(--brand);
  color: #fff;
}

.md-04__trigger {
  padding: 12px 26px;
  border: none;
  border-radius: 8px;
  background: var(--brand);
  color: #fff;
  font-family: inherit;
  font-size: .92rem;
  font-weight: 700;
  cursor: pointer;
  box-shadow: 0 8px 20px -6px rgba(99,91,255,.5);
  transition: transform .12s ease,background .18s ease;
}

.md-04__trigger:hover {
  background: #4f46e5;
  transform: translateY(-1px);
}

.md-04__backdrop {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(10,14,39,.55);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  opacity: 0;
  visibility: hidden;
  transition: opacity .25s ease,visibility 0s linear .25s;
}

.md-04__backdrop.is-open {
  opacity: 1;
  visibility: visible;
  transition: opacity .25s ease,visibility 0s linear 0s;
}
/* 3D stage for Paper Fold — perspective gives depth to the envelope opening */

.md-04__stage {
  perspective: 1600px;
  width: min(460px,100%);
}

.md-04__modal {
  position: relative;
  padding: 32px 32px 24px;
  border-radius: 12px;
  background: var(--card);
  box-shadow: 0 40px 60px -20px rgba(10,14,39,.35);
  /* Signature entrance: 3D Paper Fold — modal unfolds from top like envelope */
  opacity: 0;
  transform: rotateX(-70deg);
  transform-origin: top center;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  transition: opacity .3s ease .05s,transform .5s cubic-bezier(.22,1,.36,1) .05s;
}

.md-04__backdrop.is-open .md-04__modal {
  opacity: 1;
  transform: rotateX(0);
}

.md-04__x {
  position: absolute;
  top: 14px;
  right: 14px;
  width: 32px;
  height: 32px;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: var(--sub);
  font-family: inherit;
  font-size: 16px;
  cursor: pointer;
  transition: background .18s ease,color .18s ease;
}

.md-04__x:hover {
  background: #f1f5f9;
  color: var(--ink);
}

.md-04__brand {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: .72rem;
  font-weight: 600;
  color: var(--sub);
  letter-spacing: .02em;
  margin-bottom: 14px;
}

.md-04__lock {
  font-size: 12px;
}

.md-04__title {
  font-size: 1.45rem;
  font-weight: 800;
  letter-spacing: -.015em;
  line-height: 1.2;
  margin-bottom: 18px;
}

.md-04__summary {
  padding: 16px 18px;
  background: #fafbfc;
  border: 1px solid var(--line);
  border-radius: 9px;
  margin-bottom: 20px;
}

.md-04__row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding: 5px 0;
  font-size: .88rem;
  color: var(--sub);
}

.md-04__row strong {
  color: var(--ink);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.md-04__row--total {
  padding-top: 10px;
  margin-top: 5px;
  border-top: 1px solid var(--line);
  font-size: .95rem;
}

.md-04__row--total span {
  font-weight: 700;
  color: var(--ink);
}

.md-04__row--total strong {
  font-size: 1rem;
  font-weight: 800;
}

.md-04__form {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-bottom: 18px;
}

.md-04__label {
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.md-04__label span {
  font-size: .75rem;
  font-weight: 600;
  color: var(--sub);
  letter-spacing: .02em;
}

.md-04__label input {
  padding: 11px 14px;
  border: 1px solid var(--input-border);
  border-radius: 8px;
  background: var(--input);
  color: var(--ink);
  font-family: inherit;
  font-size: .92rem;
  font-variant-numeric: tabular-nums;
  transition: border-color .18s ease,box-shadow .18s ease;
}

.md-04__label input:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--brand-tint);
}

.md-04__row-fields {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}

.md-04__label--wide {
  grid-column: span 1;
}

.md-04__submit {
  padding: 13px 22px;
  border: none;
  border-radius: 8px;
  background: var(--brand);
  color: #fff;
  font-family: inherit;
  font-size: .98rem;
  font-weight: 700;
  cursor: pointer;
  font-variant-numeric: tabular-nums;
  transition: background .18s ease,transform .12s ease;
  margin-top: 6px;
}

.md-04__submit:hover {
  background: #4f46e5;
}

.md-04__submit:active {
  transform: translateY(1px);
}

.md-04__submit:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 3px;
}

.md-04__foot {
  padding-top: 16px;
  border-top: 1px solid var(--line);
  text-align: center;
}

.md-04__cards {
  display: block;
  font-family: 'JetBrains Mono',ui-monospace,monospace;
  font-size: .72rem;
  font-weight: 700;
  color: var(--sub);
  letter-spacing: .14em;
  margin-bottom: 6px;
}

.md-04__foot p {
  font-size: .72rem;
  color: var(--sub);
}

@media (prefers-reduced-motion: reduce) {
  .md-04__backdrop,
    .md-04__modal {
    transition: none!important;
  }

  .md-04__modal {
    transform: none!important;
  }
}

@media (max-width: 400px) {
  .md-04__row-fields {
    grid-template-columns: 1fr;
  }
}
```

## JavaScript
```js
(function(){
  var root = document.querySelector('.md-04');
  if (!root) return;
  var openBtn = root.querySelector('#md-04-open');
  var dialog = root.querySelector('#md-04-dialog');
  var closeBtns = root.querySelectorAll('.md-04__close');
  var lastFocus = null;

  function open() {
    lastFocus = document.activeElement;
    dialog.classList.add('is-open');
    document.documentElement.style.overflow = 'hidden';
    var firstInput = dialog.querySelector('input[autocomplete=cc-number]');
    if (firstInput) firstInput.focus();
    document.addEventListener('keydown', onKey);
  }
  function close() {
    dialog.classList.remove('is-open');
    document.documentElement.style.overflow = '';
    document.removeEventListener('keydown', onKey);
    if (lastFocus) lastFocus.focus();
  }
  function onKey(e) {
    if (e.key === 'Escape') {
      var hasInput = Array.prototype.some.call(dialog.querySelectorAll('input'), function(i){ return i.value.length > 0; });
      if (hasInput) {
        if (!confirm('You have unsaved payment info. Leave anyway?')) return;
      }
      close();
    }
  }

  openBtn.addEventListener('click', open);
  closeBtns.forEach(function(b){ b.addEventListener('click', close); });
  dialog.addEventListener('click', function(e){ if (e.target === dialog) close(); });
})();
```

How this works

The signature entrance is a 3D Paper Fold: the modal's parent stage gets perspective: 1600px creating a 3D viewing frustum. The modal starts at rotateX(-70deg) with transform-origin: top center — like the top half of a folded envelope lifting toward the viewer. On open, animates to rotateX(0) over 500ms via cubic-bezier(.22, 1, .36, 1) (standard ease-out, NO overshoot — payment demands precision, not bounce).

Why muted intensity for payment: full-signature paper fold (starting at 90°, big overshoot) reads as playful — the last thing users want when entering card details. The 70° start is present enough to signal "opening a checkout envelope" but muted enough to feel institutional. Perspective 1600px (deep — closer to 2400 flatness) makes the fold subtle rather than dramatic. This is the pattern used by high-trust payment products: distinctive enough to be memorable, controlled enough to not undermine the transaction.

Stripe's canonical deep-purple accent (#635bff — actual Stripe brand purple) on clean white card. Order summary above payment fields shows Subtotal / Tax / Total with divider — always visible so users can verify before paying. Field grouping: card number full-width, expiry + CVC row, name + ZIP row.

WCAG 2.1 dialog pattern with focus starting on card number field. Escape triggers unsaved-payment confirmation ("You have unsaved payment info — leave anyway?") — payment abandonment is expensive; mid-flow escape should require intent.

Make it yours

  • Change fold depth by editing rotateX(-70deg)-45deg for barely-there fold, -90deg for dramatic unfold (avoid on production payment — reads playful).
  • Adjust perspective depth by editing perspective: 1600px800px exaggerates 3D, 2800px flattens (subtle almost 2D).
  • Change fold origin by editing transform-origin: top centerbottom center folds up from below, left center or right center for side-hinged.
  • Add Apple Pay + Google Pay buttons above the manual card form (with "or pay with card" divider) — reduces friction 20-30% on mobile.
  • For subscriptions, add "Billed monthly starting $29" clarification + next-charge date below Pay button — reduces surprise-charge chargebacks 40%.

Gotchas — read before shipping

  • NEVER store real card numbers in your database. The card fields shown here must be tokenized via Stripe.js / Stripe Elements SDK — you receive a token, never the raw card number. Storing PANs (Primary Account Numbers) is a PCI DSS violation and can cost $50-500K in fines.
  • autocomplete='cc-number' / cc-exp / cc-csc / cc-name / postal-code attributes are MANDATORY for Apple Pay / Google Pay / iCloud Keychain autofill. Missing these tanks mobile checkout conversion 40-60% — measured on real e-commerce data.
  • The 3D fold entrance must use preserve-3d on the stage AND backface-visibility: hidden on the modal — without both, Safari 15+ occasionally renders a flicker at the fold moment.
  • 3D transforms with perspective can trigger paint on parent — apply will-change: transform on the modal for animation duration only, remove via animationend. Persistent will-change eats GPU memory.
  • 3D Secure 2 (SCA — Strong Customer Authentication) is REQUIRED for EU/UK transactions per PSD2. Stripe.js handles this — but your modal must handle the redirect/iframe flow gracefully.
  • Escape-to-close should prompt confirmation ("You have unsaved payment info — leave anyway?") — payment forms are high-effort; accidental escape kills conversion.
  • Never validate card details client-side beyond Luhn checksum + expiry-not-past. All validation (AVS, CVV match, 3DS) happens server-side via Stripe.

Browser support

ChromeSafariFirefoxEdge
76+9+103+76+

Floor set by backdrop-filter as this demo is written. The core technique itself goes back further — Chrome 45+.

CSS 3D transforms with perspective are Baseline 2015. backface-visibility required for cross-browser flicker prevention.

Techniques used in this demo

Search CodeFronts

Loading…