Still using Bootstrap modals or custom JavaScript? The native <dialog> element handles accessibility, focus trapping, and backdrop clicks automatically.
The Complete HTML:
<!-- The dialog element -->
<dialog id="myModal">
<form method="dialog">
<h2>Confirm Action</h2>
<p>Are you sure you want to delete this item?</p>
<div class="actions">
<button value="cancel">Cancel</button>
<button value="confirm" autofocus>Delete</button>
</div>
</form>
</dialog>
<!-- Trigger button -->
<button onclick="document.getElementById('myModal').showModal()">
Delete Item
</button>
The JavaScript (Minimal):
const dialog = document.getElementById('myModal');
// Listen for dialog close
dialog.addEventListener('close', () => {
const returnValue = dialog.returnValue;
if (returnValue === 'confirm') {
console.log('User confirmed!');
// Perform delete action
} else {
console.log('User cancelled');
}
});
// Close on backdrop click
dialog.addEventListener('click', (e) => {
if (e.target === dialog) {
dialog.close('cancel');
}
});
What You Get for Free:
✅ Focus trapping (Tab cycles only within dialog)
✅ Esc key closes the dialog
✅ Backdrop element (no separate div needed)
✅ ARIA attributes (role=”dialog”, aria-modal=”true”)
✅ Screen reader announcement
✅ Return value from form submission
✅ Stack management (multiple dialogs work correctly)
The CSS:
dialog {
padding: 2rem;
border: none;
border-radius: 8px;
box-shadow: 0 4px 16px rgba(0,0,0,0.3);
max-width: 500px;
width: 90%;
}
/* The backdrop */
dialog::backdrop {
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(3px);
}
/* Animation */
dialog[open] {
animation: slide-up 0.3s ease-out;
}
@keyframes slide-up {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Close animation */
dialog.closing {
animation: slide-down 0.3s ease-in forwards;
}
@keyframes slide-down {
to {
opacity: 0;
transform: translateY(30px);
}
}
Advanced – Prevent Accidental Close:
const dialog = document.getElementById('myModal');
dialog.addEventListener('cancel', (e) => {
// User pressed Esc
if (hasUnsavedChanges()) {
e.preventDefault(); // Block the close
if (confirm('You have unsaved changes. Close anyway?')) {
dialog.close('cancel');
}
}
});
Comparison with Bootstrap Modal:
// Bootstrap Modal
<!-- 50 lines of HTML -->
<!-- Requires Bootstrap JS: 60KB -->
<!-- Requires jQuery: 85KB -->
<!-- Total: 145KB + HTML complexity -->
// Native <dialog>
<!-- 10 lines of HTML -->
<!-- 15 lines of JavaScript -->
<!-- No libraries -->
<!-- Total: 0KB dependencies -->
Browser Support:
✅ Chrome 37+
✅ Edge 79+
✅ Firefox 98+
✅ Safari 15.4+
That’s 98%+ of all browsers. For older browsers, there’s a 2KB polyfill.
Pro Tip – Nested Dialogs:
<dialog id="outer">
<h2>Main Dialog</h2>
<button onclick="document.getElementById('inner').showModal()">
Open Nested
</button>
<dialog id="inner">
<h3>Nested Dialog</h3>
<button onclick="this.closest('dialog').close()">Close</button>
</dialog>
</dialog>
The browser automatically manages the dialog stack. Closing the inner dialog returns focus to the outer one. No manual tracking needed.
