You Might Also Like
Delete Confirmation Modal — Free HTML CSS JS Destructive Action Dialog Snippet
Delete Confirmation Modal · Modals · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Delete Confirmation Modal — HTML, CSS & JavaScript Confirmation Dialog

Any destructive, irreversible action — deleting an account, a file, or an order — deserves a confirmation step that clearly explains the consequence and makes the destructive choice visually distinct from the safe one. This snippet builds that pattern: a centered dialog with a warning icon, a clear "can't be undone" message, and two buttons where Cancel is styled neutrally and Delete is styled in a warning red, so a rushed click is far more likely to land on the safe option.
How the open/close animation works
The overlay covers the full viewport with position: fixed; inset: 0 and starts at opacity: 0; pointer-events: none. Opening the modal adds an .open class, which fades the overlay in and simultaneously animates the dialog box itself from a slightly scaled-down, offset position (scale(0.92) translateY(8px)) up to its resting scale(1) translateY(0). Keeping the overlay in the DOM at all times (rather than conditionally rendering it) and toggling opacity/pointer-events is what makes this fade/scale entrance and exit possible — an element removed via display: none cannot transition.
How focus is managed for accessibility
openModal() stores document.activeElement (whatever had focus before the modal opened, typically the trigger button) in lastFocused, then immediately moves focus onto the confirm-delete button. This does two important things: it lets keyboard and screen reader users immediately know what action is available without tabbing, and — because the destructive button most people should still deliberately click rather than hit Enter reflexively — it puts real thought in the user's path since Enter on a freshly-focused destructive button is a common source of complaints if not handled carefully; many teams instead choose to focus Cancel by default for exactly this reason, which is a one-line change here. On closeModal(), focus is returned to lastFocused, so keyboard users land back exactly where they started instead of losing their place in the page.
How Escape and click-outside dismissal work
A keydown listener is attached to document only while the modal is open (added in openModal, removed in closeModal) and closes the modal on Escape. A separate click listener on the overlay checks e.target === overlay — this is what distinguishes a click on the dark backdrop itself from a click that merely bubbled up from inside the dialog box, since clicking inside the box would also technically "hit" the overlay via event bubbling if this check weren't in place.
Why the delete button is a placeholder
confirmDelete() is deliberately left as a simple stub with a comment marking where real API logic belongs — the pattern here (modal chrome, focus handling, keyboard/click dismissal) is meant to be reused regardless of what the actual destructive action does underneath.
Build with AI
Build, Understand, Optimize, and Extend It With AI
Ask an AI assistant to explain the tradeoffs of focusing the Delete button by default versus focusing Cancel — this is a genuinely debated UX decision, and the assistant can walk through the reasoning for each and help you pick the safer default for your specific destructive action. It's also worth asking the assistant to add a full keyboard focus trap (intercepting Tab and Shift+Tab so focus cycles only within the dialog while it's open), which this snippet intentionally keeps simple by only handling initial focus and restoration.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build a "delete confirmation modal" in plain HTML, CSS, and vanilla JavaScript for confirming a destructive, irreversible action — no library, no framework.
Requirements:
- A trigger button that opens a centered modal dialog over a dimmed full-screen overlay, with a fade-in and slight scale-up entrance animation implemented via CSS transitions on opacity and transform (the overlay must remain in the DOM at all times, not be conditionally rendered).
- The dialog must use role="alertdialog", aria-modal="true", and aria-labelledby/aria-describedby pointing at its heading and description text.
- Two action buttons: a neutrally-styled Cancel button and a clearly red/destructive Delete button, visually distinct enough that a rushed click is unlikely to land on the wrong one.
- On open, move keyboard focus into the dialog. On close (by any method), return focus to whatever element had focus immediately before the dialog opened.
- The dialog must close on: clicking Cancel, clicking the dark backdrop specifically (not a click that merely bubbled up from inside the dialog box), and pressing the Escape key — with the Escape listener added only while the dialog is open and properly removed when it closes.
- Leave the actual delete action as an obviously-commented placeholder function so it is clear where real delete/API logic should be added.Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.
Step by step
How to Use
- 1Load the snippetClick "Delete Confirmation Modal" in the sidebar Library tab to load the trigger button and hidden modal.
- 2Open and inspectClick "Delete Item" in the preview and note the fade/scale entrance and that focus lands on the Delete button.
- 3Test dismissal methodsTry clicking Cancel, clicking the dark backdrop, and pressing Escape — all three should close the modal.
- 4Wire the real delete actionReplace the console.log inside confirmDelete() with your actual API call or state update, then call closeModal() after it succeeds.
- 5Customize the copyUpdate the heading and description text in the HTML panel to describe your specific item type and consequence.
- 6Export and saveExport as HTML/JSX/Tailwind or click "Save as" to reuse this modal pattern across your app's destructive actions.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
Keeping the overlay element present at all times and toggling an .open class lets the fade and scale transitions actually play. An element removed via display:none or conditional rendering has no state to animate from, so it would just appear and disappear instantly.
This snippet focuses the Delete button so keyboard users can act immediately, but many teams intentionally focus Cancel instead as a safety default, so an accidental Enter keypress does not trigger the destructive action. Both are valid choices — pick based on how catastrophic the specific delete action is.
The click listener is attached to the overlay element and checks that e.target is literally the overlay itself, not a descendant. A click inside the dialog box bubbles up through the overlay but its target is the inner element, so the check correctly ignores it.
The keydown listener is added to the document only inside openModal and explicitly removed inside closeModal, so it does not keep listening (or accumulate duplicate listeners) once the modal is no longer open.
Replace the console.log placeholder inside confirmDelete() with your actual API call or state update logic, and call closeModal() once that action completes successfully (or show an error state if it fails).
Yes — the dialog uses role="alertdialog" with aria-modal="true", and aria-labelledby/aria-describedby point to the heading and description text so screen readers announce the full context immediately when the dialog opens.
This snippet handles initial focus placement and focus restoration on close, but does not implement full Tab-key cycling containment. For a production app handling many different dialogs, consider a dedicated focus-trap utility to also intercept Tab and Shift+Tab within the dialog.