jsguides

The Dialog Element

The <dialog> element is HTML’s built-in way to create modal dialogs. Before it existed, you had to fake modals with divs, overlays, focus traps, and accessibility hacks. dialog handles most of that for free — focus management, backdrop click dismissal, form submission, the whole thing.

Browser support is solid now. If you’re not using it, you’re doing extra work for a worse result.

Basic Dialog

<dialog id="my-dialog">
  <p>Are you sure you want to delete this?</p>
  <button id="cancel-btn">Cancel</button>
  <button id="confirm-btn">Delete</button>
</dialog>
const dialog = document.getElementById("my-dialog");

// Open the dialog
dialog.showModal();

// Close it
dialog.close();

showModal() opens the dialog as a modal — it blocks interaction with the rest of the page. show() opens it as a non-modal (a popover-like panel that doesn’t block the page).

Closing the Dialog

There are a few ways to close a dialog:

// JavaScript close
dialog.close();

// Click the backdrop (modal only) — not the dialog itself
// This fires the cancelable 'close' event

// Submit a form inside the dialog

When closed, the dialog element receives a close event. The return value — if any — is stored in dialog.returnValue:

dialog.addEventListener("close", () => {
  console.log("Dialog closed with returnValue:", dialog.returnValue);
});

Passing a Return Value

Attach a <form> with method="dialog" to submit a value:

<dialog id="confirm-delete">
  <form method="dialog">
    <p>Delete this item? This cannot be undone.</p>
    <div class="dialog-actions">
      <button type="submit" value="cancel">Cancel</button>
      <button type="submit" value="delete">Delete</button>
    </div>
  </form>
</dialog>
const dialog = document.getElementById("confirm-delete");

dialog.addEventListener("close", () => {
  if (dialog.returnValue === "delete") {
    // actually delete the item
  }
});

// Open it
dialog.showModal();

The value attribute on the clicked submit button becomes dialog.returnValue.

The Backdrop

The backdrop is the semi-transparent overlay behind the modal. You can style it with ::backdrop:

dialog::backdrop {
  background-color: rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(2px);
}

The backdrop only appears for modal dialogs (showModal()). Non-modal dialogs opened with show() have no backdrop.

Focus Management

dialog handles focus automatically. When opened, focus moves to the first focusable element inside the dialog. When closed, focus returns to the element that opened it.

If you need to set initial focus to a specific element, use the autofocus attribute:

<dialog id="name-prompt">
  <form method="dialog">
    <label for="name">What's your name?</label>
    <input type="text" id="name" name="name" autofocus />
    <button type="submit">Continue</button>
  </form>
</dialog>

Positioning and Sizing

By default, the dialog centers on screen. Style it like any other element:

dialog {
  border: 1px solid #ccc;
  border-radius: 8px;
  padding: 1.5rem;
  max-width: 400px;
}

To center it vertically too:

dialog {
  margin: auto; /* this also centers horizontally */
}

Or use flexbox on the body with dialog:modal:

body {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
}

Form Integration

A <form method="dialog"> inside the dialog auto-closes the dialog on submit and sets returnValue to the button’s value. No JavaScript needed for the happy path:

<dialog id="subscribe-dialog">
  <form method="dialog">
    <label for="email">Email address</label>
    <input type="email" id="email" name="email" required />
    <button type="submit">Subscribe</button>
  </form>
</dialog>

Cancel buttons should be buttons with formmethod="dialog" to close without submitting:

<button type="button" onclick="this.closest('dialog').close()">
  Cancel
</button>

Note: type="button" is critical here — without it, the button submits the form.

Accessibility

<dialog> handles most ARIA requirements automatically:

  • Roles are set correctly (role="dialog" on the element)
  • Focus is trapped inside the modal while open
  • aria-modal="true" is applied automatically
  • Focus returns to the trigger on close

You still need to:

  • Add meaningful labels (aria-labelledby or aria-label)
  • Handle escape key (by default, closes the dialog and sets returnValue to empty string — you can’t prevent this on a modal)

Alert Dialogs

For simple confirmations, window.alert() is a blocking modal provided by the browser. You can’t style it. If you need a styled confirm box, dialog is the way:

// Custom confirm replacing window.confirm
function myConfirm(message) {
  return new Promise((resolve) => {
    const dialog = document.getElementById("confirm-dialog");
    const messageEl = document.getElementById("confirm-message");
    messageEl.textContent = message;

    dialog.showModal();

    dialog.addEventListener("close", () => {
      resolve(dialog.returnValue === "confirm");
    });
  });
}

await myConfirm("Delete this permanently?");

See Also