36 CSS Modals19 / 36

CSS + JSMIT licensed

Confirm Delete Modal

A destructive-action confirmation modal with a signature Corner Slide-and-Spin entrance — the card slides in from the bottom-right at a 3° angle then settles horizontally, giving the destructive prompt a distinct arrival that reads as intentional design rather than the standard fade-scale every SaaS uses. Sober gray + red destructive theme, item name emphasized, focus starts on Cancel (SAFE default).

Published Updated

Live Demo
Try it

The code

<div class="md-01">
  <button class="md-01__trigger" id="md-01-open">Delete report</button>
  <div class="md-01__backdrop" id="md-01-dialog" role="dialog" aria-modal="true" aria-labelledby="md-01-title" aria-describedby="md-01-desc">
    <div class="md-01__modal">
      <div class="md-01__icon" aria-hidden="true">
        <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>
      </div>
      <h2 class="md-01__title" id="md-01-title">Delete this report?</h2>
      <p class="md-01__desc" id="md-01-desc">You're about to permanently delete <strong>Q3 Financial Report.pdf</strong>. This action cannot be undone and any team members sharing this report will lose access immediately.</p>
      <div class="md-01__actions">
        <button class="md-01__btn md-01__btn--ghost md-01__close">Cancel</button>
        <button class="md-01__btn md-01__btn--danger md-01__close">Delete report</button>
      </div>
    </div>
  </div>
</div>
.md-01 {
  --page-bg: #f8fafc;
  --card: #ffffff;
  --ink: #0f172a;
  --sub: #64748b;
  --line: #e2e8f0;
  --danger: #dc2626;
  --danger-tint: rgba(220,38,38,.14);
  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-01 *,
.md-01 *::before,
.md-01 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.md-01 ::selection {
  background: var(--danger);
  color: #fff;
}

.md-01__trigger {
  padding: 11px 22px;
  border: 1px solid var(--danger);
  border-radius: 8px;
  background: transparent;
  color: var(--danger);
  font-family: inherit;
  font-size: .9rem;
  font-weight: 600;
  cursor: pointer;
  transition: background .18s ease,color .18s ease;
}

.md-01__trigger:hover {
  background: var(--danger);
  color: #fff;
}

.md-01__backdrop {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(15,23,42,.72);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  opacity: 0;
  visibility: hidden;
  transition: opacity .22s ease,visibility 0s linear .22s;
}

.md-01__backdrop.is-open {
  opacity: 1;
  visibility: visible;
  transition: opacity .22s ease,visibility 0s linear 0s;
}

.md-01__modal {
  width: min(340px,100%);
  padding: 26px 26px 22px;
  border-radius: 14px;
  background: var(--card);
  box-shadow: 0 30px 60px -20px rgba(0,0,0,.4);
  /* Signature entrance: Corner Slide-and-Spin from bottom-right corner */
  opacity: 0;
  transform: translate(24px, 32px) rotate(3deg) scale(.96);
  transform-origin: bottom right;
  transition: opacity .32s ease .04s,transform .4s cubic-bezier(.34,1.36,.44,1) .04s;
  text-align: center;
}

.md-01__backdrop.is-open .md-01__modal {
  opacity: 1;
  transform: translate(0,0) rotate(0) scale(1);
}

.md-01__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: var(--danger-tint);
  color: var(--danger);
  margin-bottom: 16px;
}

.md-01__title {
  font-size: 1.15rem;
  font-weight: 700;
  letter-spacing: -.01em;
  line-height: 1.25;
  margin-bottom: 8px;
}

.md-01__desc {
  font-size: .88rem;
  color: var(--sub);
  line-height: 1.5;
  margin-bottom: 22px;
}

.md-01__desc strong {
  color: var(--ink);
  font-weight: 600;
}

.md-01__actions {
  display: flex;
  gap: 8px;
  justify-content: stretch;
}

.md-01__btn {
  flex: 1;
  padding: 10px 14px;
  border: none;
  border-radius: 8px;
  font-family: inherit;
  font-size: .88rem;
  font-weight: 600;
  cursor: pointer;
  transition: background .18s ease,transform .12s ease;
}

.md-01__btn:active {
  transform: translateY(1px);
}

.md-01__btn:focus-visible {
  outline: 2px solid var(--danger);
  outline-offset: 2px;
}

.md-01__btn--ghost {
  background: #f1f5f9;
  color: var(--ink);
}

.md-01__btn--ghost:hover {
  background: #e2e8f0;
}

.md-01__btn--danger {
  background: var(--danger);
  color: #fff;
}

.md-01__btn--danger:hover {
  background: #b91c1c;
}

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

  .md-01__modal {
    transform: none!important;
  }
}
(function(){
  var root = document.querySelector('.md-01');
  if (!root) return;
  var openBtn = root.querySelector('#md-01-open');
  var dialog = root.querySelector('#md-01-dialog');
  var closeBtns = root.querySelectorAll('.md-01__close');
  var lastFocus = null;

  function open() {
    lastFocus = document.activeElement;
    dialog.classList.add('is-open');
    document.documentElement.style.overflow = 'hidden';
    var cancel = dialog.querySelector('.md-01__btn--ghost');
    if (cancel) cancel.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') 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: Confirm Delete Modal
Source: https://codefronts.com/components/css-modals/confirm-delete-modal/

A destructive-action confirmation modal with a signature Corner Slide-and-Spin entrance — the card slides in from the bottom-right at a 3° angle then settles horizontally, giving the destructive prompt a distinct arrival that reads as intentional design rather than the standard fade-scale every SaaS uses. Sober gray + red destructive theme, item name emphasized, focus starts on Cancel (SAFE default).
## HTML
```html
<div class="md-01">
  <button class="md-01__trigger" id="md-01-open">Delete report</button>
  <div class="md-01__backdrop" id="md-01-dialog" role="dialog" aria-modal="true" aria-labelledby="md-01-title" aria-describedby="md-01-desc">
    <div class="md-01__modal">
      <div class="md-01__icon" aria-hidden="true">
        <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>
      </div>
      <h2 class="md-01__title" id="md-01-title">Delete this report?</h2>
      <p class="md-01__desc" id="md-01-desc">You're about to permanently delete <strong>Q3 Financial Report.pdf</strong>. This action cannot be undone and any team members sharing this report will lose access immediately.</p>
      <div class="md-01__actions">
        <button class="md-01__btn md-01__btn--ghost md-01__close">Cancel</button>
        <button class="md-01__btn md-01__btn--danger md-01__close">Delete report</button>
      </div>
    </div>
  </div>
</div>
```
## CSS
```css
.md-01 {
  --page-bg: #f8fafc;
  --card: #ffffff;
  --ink: #0f172a;
  --sub: #64748b;
  --line: #e2e8f0;
  --danger: #dc2626;
  --danger-tint: rgba(220,38,38,.14);
  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-01 *,
.md-01 *::before,
.md-01 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.md-01 ::selection {
  background: var(--danger);
  color: #fff;
}

.md-01__trigger {
  padding: 11px 22px;
  border: 1px solid var(--danger);
  border-radius: 8px;
  background: transparent;
  color: var(--danger);
  font-family: inherit;
  font-size: .9rem;
  font-weight: 600;
  cursor: pointer;
  transition: background .18s ease,color .18s ease;
}

.md-01__trigger:hover {
  background: var(--danger);
  color: #fff;
}

.md-01__backdrop {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(15,23,42,.72);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  opacity: 0;
  visibility: hidden;
  transition: opacity .22s ease,visibility 0s linear .22s;
}

.md-01__backdrop.is-open {
  opacity: 1;
  visibility: visible;
  transition: opacity .22s ease,visibility 0s linear 0s;
}

.md-01__modal {
  width: min(340px,100%);
  padding: 26px 26px 22px;
  border-radius: 14px;
  background: var(--card);
  box-shadow: 0 30px 60px -20px rgba(0,0,0,.4);
  /* Signature entrance: Corner Slide-and-Spin from bottom-right corner */
  opacity: 0;
  transform: translate(24px, 32px) rotate(3deg) scale(.96);
  transform-origin: bottom right;
  transition: opacity .32s ease .04s,transform .4s cubic-bezier(.34,1.36,.44,1) .04s;
  text-align: center;
}

.md-01__backdrop.is-open .md-01__modal {
  opacity: 1;
  transform: translate(0,0) rotate(0) scale(1);
}

.md-01__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: var(--danger-tint);
  color: var(--danger);
  margin-bottom: 16px;
}

.md-01__title {
  font-size: 1.15rem;
  font-weight: 700;
  letter-spacing: -.01em;
  line-height: 1.25;
  margin-bottom: 8px;
}

.md-01__desc {
  font-size: .88rem;
  color: var(--sub);
  line-height: 1.5;
  margin-bottom: 22px;
}

.md-01__desc strong {
  color: var(--ink);
  font-weight: 600;
}

.md-01__actions {
  display: flex;
  gap: 8px;
  justify-content: stretch;
}

.md-01__btn {
  flex: 1;
  padding: 10px 14px;
  border: none;
  border-radius: 8px;
  font-family: inherit;
  font-size: .88rem;
  font-weight: 600;
  cursor: pointer;
  transition: background .18s ease,transform .12s ease;
}

.md-01__btn:active {
  transform: translateY(1px);
}

.md-01__btn:focus-visible {
  outline: 2px solid var(--danger);
  outline-offset: 2px;
}

.md-01__btn--ghost {
  background: #f1f5f9;
  color: var(--ink);
}

.md-01__btn--ghost:hover {
  background: #e2e8f0;
}

.md-01__btn--danger {
  background: var(--danger);
  color: #fff;
}

.md-01__btn--danger:hover {
  background: #b91c1c;
}

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

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

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

  function open() {
    lastFocus = document.activeElement;
    dialog.classList.add('is-open');
    document.documentElement.style.overflow = 'hidden';
    var cancel = dialog.querySelector('.md-01__btn--ghost');
    if (cancel) cancel.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') 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 Corner Slide-and-Spin: modal starts at translate(24px, 32px) rotate(3deg) scale(.96) — offset toward bottom-right and slightly rotated. On open, animates to translate(0, 0) rotate(0) scale(1) over 400ms via cubic-bezier(.34, 1.36, .44, 1) — a soft spring with mild overshoot (1.36 above 1 gives 4-5% pass-through). The result: the modal appears to swing in from the corner and settle into place, distinct from every standard center-fade modal on the web.

Why muted intensity for destructive: pure playful entrances (rotate 15°+, big overshoot) read as frivolous on "Are you sure you want to delete?" prompts. Users need the moment to feel weighty. The 3° rotate is subtle enough to feel intentional but not silly. Sober gray palette (#fafafa card + #0f172a ink + #64748b sub) + destructive red (#dc2626) — GitHub / Linear / Notion conventions.

WCAG 2.1 dialog pattern: role='dialog' + aria-modal='true' + aria-labelledby. Focus starts on Cancel button (SAFE default — Enter reflex must not delete). Escape closes. Backdrop click closes. document.documentElement.style.overflow='hidden' locks body scroll. Focus returns to trigger on close.

Make it yours

  • Change the entrance angle by editing rotate(3deg) in @keyframes md-01-in5deg for a more pronounced spin, 1deg for an almost-invisible tilt.
  • Reverse the corner direction by flipping the translate signs — translate(-24px, 32px) rotate(-3deg) for bottom-left, translate(24px, -32px) rotate(-3deg) for top-right.
  • Change the spring overshoot by editing cubic-bezier(.34, 1.36, .44, 1) — replace the 1.36 with 1.56 for more bounce, 1.15 for a gentler settle.
  • For extra-destructive actions (repository deletion, account termination), add a type-to-confirm input ("Type 'DELETE' to confirm") — pattern used by GitHub for repository deletion.
  • Recolor destructive theme by editing --danger — orange (#ea580c) for warnings that aren't irreversible, purple (#7c3aed) for authorization prompts.

Gotchas — read before shipping

  • Focus MUST start on the safe action (Cancel), never the destructive action. Users pressing Enter reflexively should not accidentally delete. This is a WCAG usability requirement, not just a nice-to-have.
  • The 3° rotate + 1.36 overshoot is deliberately MUTED because destructive prompts need to feel weighty. Copying a full-signature entrance (10° rotate, 1.56 overshoot) onto a payment or delete modal reads as frivolous and reduces perceived trust.
  • Include the item name in the modal (<strong>Q3 Financial Report.pdf</strong>) — generic "Delete this item?" causes accidental confirmations when users forget which item they clicked.
  • The Escape key MUST close the modal, and backdrop click MUST close it (checked via event.target === backdrop). Missing either is WCAG 2.1 SC 2.1.1 violation.
  • Body scroll lock (overflow: hidden on <html>) is critical — otherwise users can scroll the page behind the modal, breaking the modal illusion.
  • For irreversibly destructive actions in enterprise SaaS, consider a 5-second cooldown before enabling the Delete button — pattern used by AWS console. Prevents impulsive deletions.

Browser support

ChromeSafariFirefoxEdge
76+15.4+103+76+

backdrop-filter for backdrop blur. Falls back to solid backdrop on older browsers.

Techniques used in this demo

Search CodeFronts

Loading…