Blog Build an accessible modal dialog with plain HTML
Build an accessible modal dialog with plain HTML
TL;DR Use the native dialog element with showModal(). It gives you focus trapping, Escape to close, and a backdrop for free. Then add the finishing touches: a clear label, focus that returns to the trigger on close, and a visible focus style. Far less code than a hand-rolled modal, and more correct.
Modals are one of the most commonly botched components on the web. Focus leaks to the page behind, Escape does nothing, and a screen reader user has no idea what opened. The good news is that the native dialog element now does the hard parts for you.
Start with the native element
<button id="open">Open dialog</button>
<dialog id="box" aria-labelledby="title">
<h2 id="title">Confirm action</h2>
<p>Are you sure?</p>
<button id="close">Cancel</button>
</dialog>
Open and close it with the modal methods:
const box = document.getElementById("box");
document.getElementById("open").addEventListener("click", () => box.showModal());
document.getElementById("close").addEventListener("click", () => box.close());
showModal() is the important call. It traps focus inside the dialog, closes on Escape, renders a backdrop, and makes the rest of the page inert. Those are exactly the behaviours people hand-roll and get wrong.
Add the finishing touches
The element gives you the mechanics; a few details make it genuinely accessible.
- Label it.
aria-labelledbypointing at the heading tells screen readers what the dialog is. - Return focus. When the dialog closes, move focus back to the button that opened it, so keyboard users are not dumped at the top of the page.
- Show focus. Keep a visible focus outline inside the dialog, so keyboard users can see where they are.
box.addEventListener("close", () => document.getElementById("open").focus());
Style the backdrop
The backdrop is stylable with a pseudo-element, no extra markup:
dialog::backdrop { background: rgba(0, 0, 0, 0.5); }
Why this beats a div
A modal built from a plain div means you personally implement focus trapping, Escape handling, background inertness, and the backdrop, and you maintain all of it. The native dialog hands you the correct behaviour and lets you spend your effort on the label, the focus return, and the styling. Less code, fewer bugs, and a modal that actually works for keyboard and screen-reader users, which is the whole point.
FAQ
Do I need JavaScript for an accessible modal?
A little, to open and close it, but far less than before. The native dialog element handles focus trapping, Escape to close, and the backdrop itself, so you are not reimplementing the hard accessibility parts by hand.
What makes a modal inaccessible?
Common failures: keyboard focus escaping to the page behind it, no way to close with Escape, focus not returning to what opened it, and no label for screen readers. A hand-rolled div modal usually misses several of these.
Why use dialog instead of a div?
The dialog element with showModal gives you the correct behaviour, focus trap, Escape, backdrop, inert background, without you rebuilding it. A plain div modal means writing and maintaining all of that yourself, and most implementations get some of it wrong.