Learn how to create native modal and non-modal dialogs using the HTML dialog element, with built-in focus trapping, backdrop styling, and form integration.
The <dialog> element represents a dialog box or popup window. It is hidden by default and can be shown in two modes:
showModal()) — Overlays the entire page with a backdrop, traps focus inside the dialog, and blocks interaction with the rest of the page. Press Escape to close.show()) — Opens without a backdrop. The user can still interact with the rest of the page.<dialog id="myDialog">
<h2>Dialog Title</h2>
<p>This is a native HTML dialog.</p>
<button onclick="this.closest('dialog').close()">Close</button>
</dialog>
<button onclick="document.getElementById('myDialog').showModal()">
Open Modal
</button>The dialog is a standard HTML element — no JavaScript library needed for modals!
dialog.showModal() — Opens as a modal with backdrop and focus trap.dialog.show() — Opens as a non-modal dialog.open attribute — Adding it directly makes the dialog visible (non-modal), but showModal() is preferred for modals.dialog.close() — Closes the dialog.dialog.close('result') — Closes and sets dialog.returnValue to the passed string.method="dialog" inside the dialog closes it on submit.close eventdialog.addEventListener('close', () => {
console.log('Return value:', dialog.returnValue);
});When a form inside a dialog has method="dialog", submitting it closes the dialog. The submit button's value becomes the dialog's returnValue:
<dialog id="confirm">
<form method="dialog">
<p>Are you sure?</p>
<button value="yes">Yes</button>
<button value="no">No</button>
</form>
</dialog>Modal dialogs create a backdrop pseudo-element that covers the page behind the dialog. You can style it with the ::backdrop pseudo-element:
dialog::backdrop {
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(4px);
}The dialog itself can be styled like any element:
dialog {
border: none;
border-radius: 12px;
padding: 2rem;
max-width: 500px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
}Modern browsers handle focus trapping automatically for modal dialogs — focus cannot leave the dialog while it is open, and focus returns to the trigger element when it closes.
The <dialog> element has excellent built-in accessibility:
role="dialog" is implicit — screen readers announce it as a dialog.aria-label or aria-labelledby to provide an accessible name:<dialog aria-labelledby="dialog-title">
<h2 id="dialog-title">Confirm Deletion</h2>
<p>This action cannot be undone.</p>
</dialog>showModal() — focus moves into the dialog and is trapped.show()), you may need to manage focus manually.Always ensure the dialog has a clear way to be dismissed (a close button, cancel button, or form submission).
<!-- Trigger button -->
<button id="openBtn">Delete Account</button>
<!-- Modal dialog -->
<dialog id="confirmDialog" aria-labelledby="dlg-title">
<h2 id="dlg-title">Confirm Deletion</h2>
<p>Are you sure you want to delete your account? This cannot be undone.</p>
<form method="dialog">
<button value="cancel">Cancel</button>
<button value="confirm">Delete</button>
</form>
</dialog>
<script>
const dialog = document.getElementById('confirmDialog');
const openBtn = document.getElementById('openBtn');
openBtn.addEventListener('click', () => dialog.showModal());
dialog.addEventListener('close', () => {
if (dialog.returnValue === 'confirm') {
console.log('Account deleted');
}
});
</script>What is the difference between dialog.show() and dialog.showModal()?