< Modern HTML >

< dialog >

The dialog element: modals, built in

A native modal and non-modal dialog element. HTML gives you the top layer, focus trapping, inert background content, Escape-to-close handling, backdrop styling, and proper focus return when the dialog closes. The dialog element has been shipping in all three major browser engines since early 2022 and has matured meaningfully in the years since.

Built-in with native HTML

Modal vs non-modal: two open modes, two different behaviors

The dialog element has two JavaScript methods for opening, and they produce genuinely different user experiences.

Behavior show() (non-modal) showModal() (modal)
Renders in top layer No Yes
Background inert No Yes
Escape closes No Yes
Light-dismiss on backdrop No No (opt-in)
::backdrop renders No Yes
Focus trapped inside No Yes

Most UI that uses the word "modal" wants showModal(). Setting the open attribute in HTML produces a non-modal dialog. Modals specifically require the JavaScript call, because the modal behavior depends on the dialog being promoted to the top layer at runtime.

method="dialog" on a form inside the dialog

A form inside a dialog can use a special form method: dialog. When a form with method="dialog" is submitted:

This turns a confirmation dialog into one element with two buttons and one event listener:

<dialog id="confirm">
  <form method="dialog">
    <p>Delete this item?</p>
    <button value="cancel">Cancel</button>
    <button value="confirm">Delete</button>
  </form>
</dialog>

<script>
  const dlg = document.getElementById('confirm');
  document.querySelector('#delete-btn').addEventListener('click', () => dlg.showModal());
  dlg.addEventListener('close', () => {
    if (dlg.returnValue === 'confirm') {
      // perform the delete
    }
  });
</script>

One event handler for the whole dialog, regardless of how many options it offers. This is often a nice simplification over wiring separate click handlers for each button.

Styling the backdrop

The backdrop is a real pseudo-element that CSS can style:

dialog::backdrop {
  background: rgb(0 0 0 / 0.6);
  backdrop-filter: blur(4px);
}

dialog[open] {
  animation: dialog-in 200ms ease-out;
}

@starting-style {
  dialog[open] {
    opacity: 0;
    transform: scale(0.95);
  }
}

The @starting-style rule, paired with transition-behavior: allow-discrete, animates the dialog from nothing as it enters the DOM. Because dialogs transition from display: none to display: block, which is a discrete change, standard CSS transitions on the dialog itself don't cover enter animations. The starting-style rule fills that gap.

Focus management details

When showModal() runs:

When the dialog closes:

The focus-return behavior is tracked through the top-layer mechanism, which makes it more resilient to DOM changes than a JavaScript reference to the previously-focused element would be.

Light-dismiss (click-outside-to-close)

Not on by default. The common pattern is one line:

dialog.addEventListener('click', (event) => {
  if (event.target === dialog) dialog.close();
});

This works because clicks on content inside the dialog bubble with event.target set to the inner element, while clicks on the backdrop area register with event.target as the dialog itself. The check distinguishes the two.

A newer closedby attribute is in active development that would make light-dismiss declarative. Worth checking current browser support before relying on it.

Invoker commands with <dialog>

The invoker commands API lets a button open a dialog declaratively, with no click handler on the trigger:

<button commandfor="confirm" command="show-modal">Delete item</button>
<dialog id="confirm">...</dialog>

Paired with <form method="dialog"> inside the dialog, a confirmation modal is possible with zero JavaScript on either the trigger or the buttons. The close event on the dialog is still the place to handle what the user chose. Invoker commands are newer than the dialog element itself, so current browser support is worth verifying before relying on them in production.

Non-modal dialog use cases

The non-modal variant, opened with show() instead of showModal(), is for dialogs that should not block interaction with the rest of the page:

The rule of thumb: if the user needs to keep interacting with the rest of the page while the dialog is visible, it's non-modal. If the dialog demands a decision before anything else happens, it's modal.

Composite patterns where <dialog> is the right element

Already HTML-available

Patterns that HTML handles natively, which are often rebuilt with libraries or custom code:


Reference:

WHATWG
the-dialog-element
caniuse.com
mdn-html_elements_dialog
MDN Docs
Web/HTML/Element/dialog
CSS: @starting-style
CSS: ::backdrop
WAI-ARIA APG
Dialog (Modal) Pattern