Modal dialogs
<dialog> with showModal()
Chrome37
Edge79
Firefox98
Safari15.4
Features it needs
- <dialog>Widely available
showModal() gives you the things a modal library exists to provide: the top layer, so no z-index fights, a real backdrop you style with ::backdrop, focus moved into the dialog and restored on close, a focus trap while it is open, Escape to dismiss, and inert content behind it. The parts still worth writing yourself are click-outside-to-close and the open and close transitions.
When this applies
Building a modal dialog, confirmation prompt or alert.
The native approach
<dialog id="confirm">
<form method="dialog">
<h2>Delete this project?</h2>
<button value="cancel">Cancel</button>
<button value="confirm">Delete</button>
</form>
</dialog>
<script>
document.querySelector("#confirm").showModal();
</script>
<style>
dialog::backdrop {
background: rgb(0 0 0 / 0.5);
backdrop-filter: blur(2px);
}
</style>When the dependency is still right
An answer that always says "the platform covers it" is worse than no answer. These are the cases where this one does not hold.
- You need click-on-the-backdrop to close. <dialog> does not do this, so you still write a small click handler that compares the event target.
- You need enter and exit transitions and must support browsers without @starting-style and transition-behavior: allow-discrete.
- You need a non-modal overlay that leaves the page interactive. Use popover instead of showModal().
- You render the dialog through a portal into a container that is itself inside a transform or filter, which creates a containing block and breaks top-layer assumptions.
- Your framework's dialog component is already handling scroll locking and route-driven open state, and the migration is not worth the churn.
Building it
This catalog stops at the swap. These guides go through the implementation and the fallbacks, and each one reads in the terminal through its own command.
- Light dismiss a dialog
npx -y modern-web-guidance@latest retrieve "light-dismiss-a-dialog" - Declarative dialog popover control
npx -y modern-web-guidance@latest retrieve "declarative-dialog-popover-control" - Platform controls dismiss dialog
npx -y modern-web-guidance@latest retrieve "platform-controls-dismiss-dialog" - Animate to from top layer
npx -y modern-web-guidance@latest retrieve "animate-to-from-top-layer"
Signs it was hand-rolled
No package is involved in any of these, so nothing would match in a package.json. If the code looks like one of them, this rule applies anyway, and the conditions above still decide.
- a div with role=dialog and aria-modal, placed with position fixed and a z-index over the page
- a document keydown listener added when an overlay opens and removed when it closes, only to catch Escape