About this snippet

A destructive action opens a confirmation modal. Focus moves into the dialog, Escape or a click on the backdrop cancels, and the two buttons return a value the page can act on.

The modal is a native dialog element opened with showModal, which is what gives it the focus trap, the Escape key and the ::backdrop pseudo-element for free. The buttons sit inside a form with method set to dialog, so pressing one closes the dialog and stores that button's value in returnValue without any click handlers. A click on the backdrop reports the dialog itself as the target while a click inside reports its content, which is how the two are told apart.

The entrance is a CSS transition. @starting-style provides the hidden state to transition from, and allow-discrete on display and overlay lets the fade out finish before the dialog disappears.

Size
3.9 KB
Uses
HTML, CSS, JavaScript
Loads
Nothing external
Views
37
<div class="card">
  <h1>Project settings</h1>
  <p>Deleting a project removes every file, deployment and log attached to it.</p>
  <button type="button" class="btn danger" id="open-dialog">Delete project</button>
  <p class="status" id="status" aria-live="polite"></p>
</div>

<dialog class="modal" id="confirm-dialog" aria-labelledby="dialog-title">
  <form method="dialog">
    <div class="icon">
      <svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18M8 6V4h8v2M19 6l-1 14H6L5 6M10 11v6M14 11v6"></path></svg>
    </div>
    <h2 id="dialog-title">Delete this project?</h2>
    <p>This cannot be undone. Backups are kept for 30 days on paid plans.</p>
    <div class="actions">
      <button type="submit" value="cancel" class="btn ghost">Keep project</button>
      <button type="submit" value="delete" class="btn danger" autofocus>Delete</button>
    </div>
  </form>
</dialog>
*, *::before, *::after {
  box-sizing: border-box;
}

body {
  font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  margin: 0;
  background: #f1f5f9;
  color: #0f172a;
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
  padding: 20px;
}

.card {
  background: #ffffff;
  width: 100%;
  max-width: 440px;
  padding: 36px;
  border-radius: 20px;
  box-shadow: 0 20px 40px -20px rgba(15, 23, 42, 0.25);
}

.card h1 {
  font-size: 1.375rem;
  margin: 0 0 8px;
}

.card p {
  margin: 0 0 22px;
  color: #475569;
  line-height: 1.6;
}

.status {
  min-height: 1.5em;
  margin: 18px 0 0 !important;
  font-size: 0.875rem;
  font-weight: 500;
}

.btn {
  font: inherit;
  font-weight: 600;
  padding: 11px 18px;
  border: 0;
  border-radius: 10px;
  cursor: pointer;
  transition: background 0.15s ease, transform 0.15s ease;
}

.btn:active {
  transform: scale(0.98);
}

.btn.danger {
  background: #dc2626;
  color: #ffffff;
}

.btn.danger:hover {
  background: #b91c1c;
}

.btn.ghost {
  background: #f1f5f9;
  color: #0f172a;
}

.btn.ghost:hover {
  background: #e2e8f0;
}

.modal {
  width: min(92vw, 400px);
  padding: 0;
  border: 0;
  border-radius: 20px;
  background: #ffffff;
  box-shadow: 0 30px 60px -20px rgba(15, 23, 42, 0.45);
  opacity: 0;
  translate: 0 12px;
  transition: opacity 0.22s ease, translate 0.22s ease, overlay 0.22s allow-discrete, display 0.22s allow-discrete;
}

.modal[open] {
  opacity: 1;
  translate: 0 0;
}

@starting-style {
  .modal[open] {
    opacity: 0;
    translate: 0 12px;
  }
}

.modal::backdrop {
  background: rgba(15, 23, 42, 0.45);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  transition: opacity 0.22s ease, overlay 0.22s allow-discrete, display 0.22s allow-discrete;
  opacity: 0;
}

.modal[open]::backdrop {
  opacity: 1;
}

@starting-style {
  .modal[open]::backdrop {
    opacity: 0;
  }
}

.modal form {
  padding: 32px;
  text-align: center;
}

.icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: #fef2f2;
  color: #dc2626;
  margin-bottom: 16px;
}

.modal h2 {
  font-size: 1.25rem;
  margin: 0 0 8px;
}

.modal p {
  margin: 0 0 24px;
  color: #475569;
  line-height: 1.6;
}

.actions {
  display: flex;
  gap: 10px;
  justify-content: center;
}

.actions .btn {
  flex: 1;
}
const dialog = document.getElementById("confirm-dialog");
const openButton = document.getElementById("open-dialog");
const status = document.getElementById("status");

openButton.addEventListener("click", function() {
  dialog.showModal();
});

dialog.addEventListener("click", function(e) {
  if (e.target === dialog) {
    dialog.close("cancel");
  }
});

dialog.addEventListener("close", function() {
  if (dialog.returnValue === "delete") {
    status.textContent = "Project deleted.";
    status.style.color = "#dc2626";
  } else {
    status.textContent = "Nothing was changed.";
    status.style.color = "#475569";
  }
});
Esc
↑↓ moveEnter select

Start from a template

Open an .html file, or HTML, CSS, JS, Markdown, Less or TypeScript files. You can also drop them here. Styles and scripts go into their panes.

Share

Source

Keyboard shortcuts

Save