HTMLDialogs & Modals (<dialog>)

The <dialog> Element

For years, "modal dialog" meant a <div> positioned with CSS, plus hand-written JavaScript to trap focus, handle Escape, and manage the backdrop. <dialog> builds all of that into the browser: a native, accessible modal (or non-modal) window with almost no custom code required.
Basic Markup

dialog-basic.html

HTML
<dialog id="welcome-dialog">
  <h2>Welcome!</h2>
  <p>Thanks for signing up. Let's get you started.</p>
  <button id="close-btn">Got it</button>
</dialog>

<button id="open-btn">Open dialog</button>

<script>
  const dialog = document.getElementById('welcome-dialog');
  document.getElementById('open-btn').addEventListener('click', () => {
    dialog.showModal();
  });
  document.getElementById('close-btn').addEventListener('click', () => {
    dialog.close();
  });
</script>
A <dialog> renders invisible by default (like display: none) until it's opened with showModal() or show().
showModal() vs show()

Method

Behavior

showModal()

Opens as a true modal: traps focus inside, dims the rest of the page with a backdrop, disables interaction outside the dialog, and closes on Escape

show()

Opens as a non-modal dialog: no backdrop, no focus trap, the rest of the page stays interactive

Most use cases—confirmation prompts, forms, alerts—want showModal(). Reach for show() only for lightweight, non-blocking panels (like a "tips" popover) where the user should still be able to interact with the rest of the page.
The ::backdrop Pseudo-Element
When opened with showModal(), the browser renders a ::backdrop layer behind the dialog, covering the whole viewport. You can style it directly with CSS.

backdrop.html

HTML
<style>
  dialog::backdrop {
    background: rgb(0 0 0 / 0.5);
    backdrop-filter: blur(2px);
  }
</style>

<dialog id="confirm-dialog">
  <p>Delete this item permanently?</p>
  <button id="cancel-btn">Cancel</button>
  <button id="delete-btn">Delete</button>
</dialog>
Closing With a method="dialog" Form
If the dialog contains a <form> with method="dialog", submitting that form automatically closes the dialog—no JavaScript required for the close action itself. The button that triggered submission is recorded as the dialog's returnValue.

form-dialog.html

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

<script>
  const dialog = document.getElementById('confirm-dialog');
  dialog.addEventListener('close', () => {
    if (dialog.returnValue === 'confirm') {
      console.log('User confirmed deletion');
    }
  });
</script>
method=dialog also works for lightweight prompts
This pattern is a clean way to build a "confirm" dialog without manually wiring up click handlers on every button—submit closes it, and returnValue tells you which button was pressed.
Closing on Backdrop Click
<dialog> doesn't close on backdrop click by default. A common pattern checks whether the click target was the dialog itself (the backdrop area) rather than its content.

backdrop-click.html

HTML
<script>
  dialog.addEventListener('click', (event) => {
    if (event.target === dialog) {
      dialog.close();
    }
  });
</script>
Why Native Beats a Div-Based Modal
Hand-rolled <div> modals require you to manually implement focus trapping (keeping Tab from escaping the modal), Escape-to-close, backdrop dimming, and correct ARIA roles. Get any of these wrong and keyboard or screen reader users get stuck or lost. <dialog> with showModal() handles all of it correctly out of the box.
show() does not trap focus
If accessibility matters for your dialog—and it usually does—showModal() is almost always the right call. show() is meant for non-blocking, dismissible panels only.
Still style it like any other element
<dialog> is positioned by the browser (centered, fixed) by default, but you can override its position, size, and appearance with ordinary CSS just like any other element.
Focus Management on Open
When showModal() runs, the browser automatically moves focus to the first focusable element inside the dialog (or the dialog itself if nothing inside is focusable). When the dialog closes, focus automatically returns to whatever element opened it—no manual bookkeeping required.

focus-flow.html

HTML
<button id="open-btn">Delete account</button>

<dialog id="confirm-dialog">
  <form method="dialog">
    <p>This action cannot be undone. Continue?</p>
    <button value="cancel" autofocus>Cancel</button>
    <button value="confirm">Delete</button>
  </form>
</dialog>

<script>
  document.getElementById('open-btn').addEventListener('click', () => {
    document.getElementById('confirm-dialog').showModal();
    // Focus already lands on the "Cancel" button via autofocus.
    // Closing the dialog returns focus to "Delete account" automatically.
  });
</script>
Adding autofocus to the safer default action (here, "Cancel") is a good practice—it means pressing Enter immediately after the dialog opens does the safe thing, not the destructive one.
Dialog as a Non-Modal Notification

non-modal-toast.html

HTML
<dialog id="toast" style="position: fixed; bottom: 24px; right: 24px;">
  <p>Saved successfully.</p>
</dialog>

<script>
  const toast = document.getElementById('toast');
  toast.show();
  setTimeout(() => toast.close(), 3000);
</script>
Because show() doesn't trap focus or dim the page, it's appropriate here—the user should be able to keep working while a transient confirmation message appears and disappears on its own.
Progressive Enhancement Note
<dialog> has broad support in current browsers, but if you must support very old browsers, feature-detect before relying on it.

feature-detect.html

HTML
<script>
  if (typeof HTMLDialogElement === 'function') {
    // Safe to use dialog.showModal()
  } else {
    // Fall back to a hand-built modal implementation
  }
</script>
  • showModal() opens a true modal: focus trap, backdrop, Escape-to-close, disabled background interaction.

  • show() opens a non-modal panel with none of those behaviors.

  • Style the modal backdrop with the ::backdrop pseudo-element.

  • A nested <form method="dialog"> closes the dialog on submit and records which button was pressed via returnValue.

  • Native <dialog> avoids the focus-trapping and ARIA bugs common in hand-built div-based modals.