Modal dialogs

<dialog> with showModal()

Baseline widely available
  • 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>

MDN reference

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.

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

Packages this covers