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-labelledbyoraria-label) - Handle escape key (by default, closes the dialog and sets
returnValueto 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
- /guides/javascript-popover-api/ — popover API for tooltips and floating UI
- /guides/javascript-shadow-dom/ — encapsulating styles and markup with shadow DOM
- /tutorials/js-the-dom/ — DOM fundamentals