The <dialog> Element
Build native modal and non-modal dialogs with the HTML dialog element, focus management, and the showModal/show/close API.
Overview
Key Points
- •`showModal()` creates a top-layer modal with focus trap; `show()` creates an inline non-modal dialog
- •The `::backdrop` pseudo-element styles the overlay behind a modal dialog
- •The `close` event fires when the dialog closes; `dialog.returnValue` holds the value of the closing button
- •A `<form method="dialog">` inside a dialog closes it on submit and sets returnValue to the submitter button's value
- •Use `autofocus` on the first interactive element inside the dialog for correct focus placement
- •Escape key automatically closes modal dialogs; you can cancel it via the `cancel` event
- •The `open` attribute reflects whether the dialog is visible; don't set it directly use the JS methods
Examples
Full modal dialog with open/close and return value
<button id="open-btn">Open Dialog</button>
<dialog id="confirm-dialog" aria-labelledby="dlg-title">
<h2 id="dlg-title">Confirm Action</h2>
<p>Are you sure you want to delete this item?</p>
<form method="dialog">
<button value="cancel" autofocus>Cancel</button>
<button value="confirm">Delete</button>
</form>
</dialog>
<script>
const dialog = document.getElementById('confirm-dialog');
const openBtn = document.getElementById('open-btn');
openBtn.addEventListener('click', () => dialog.showModal());
dialog.addEventListener('close', () => {
if (dialog.returnValue === 'confirm') {
console.log('User confirmed deletion');
}
});
</script>The form method="dialog" automatically closes the dialog. The clicked button's value becomes dialog.returnValue.
Styled backdrop and dialog transitions
<style>
dialog {
border: none;
border-radius: 12px;
padding: 2rem;
max-width: 480px;
box-shadow: 0 25px 50px rgba(0,0,0,0.25);
}
dialog::backdrop {
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px);
}
/* Animate in */
dialog[open] {
animation: slide-in 0.2s ease-out;
}
@keyframes slide-in {
from { opacity: 0; transform: translateY(-20px); }
to { opacity: 1; transform: translateY(0); }
}
</style>
<dialog id="styled-dialog">
<h2>Settings</h2>
<p>Configure your preferences below.</p>
<form method="dialog">
<button>Close</button>
</form>
</dialog>The ::backdrop pseudo-element only appears for modal dialogs opened with showModal().
Preventing close and handling the cancel event
<dialog id="form-dialog">
<h2>Edit Profile</h2>
<form method="dialog" id="profile-form">
<label>Name <input type="text" name="name" required></label>
<button type="submit" value="save">Save</button>
<button type="button" id="cancel-btn">Cancel</button>
</form>
</dialog>
<script>
const dialog = document.getElementById('form-dialog');
// Prevent Escape key from closing if there are unsaved changes
dialog.addEventListener('cancel', (event) => {
if (hasUnsavedChanges()) {
event.preventDefault();
if (confirm('Discard unsaved changes?')) {
dialog.close('cancelled');
}
}
});
document.getElementById('cancel-btn').addEventListener('click', () => {
dialog.close('cancelled');
});
</script>Calling event.preventDefault() on the cancel event stops Escape from closing the dialog.
Common Mistakes
- Using `display:none` / `display:block` to show/hide dialogs instead of the JS methods this bypasses focus management and the top layer
- Forgetting `aria-labelledby` or `aria-label` on the dialog screen readers need a label to announce the dialog purpose
- Not using `autofocus` on the first interactive element keyboard users land on the wrong element
Note
For a close button that does not submit a form, use `<button type="button" onclick="dialog.close()">` using `type="submit"` inside `method="dialog"` closes the dialog but also submits any outer form.
Exercises
Write HTML to solve each exercise. Use Preview to see it render in a live iframe, then mark as done.
Completed: 0 / 5 exercises
Knowledge Check
Question 1 of 4