Simpler and Better Modals
All modern browsers now include a built-in building block that helps us create accessible modals with less code. If you, like me, had no idea this existed, then this post is for you.
Published June 7 2023
All modern browsers now include a built-in building block that helps us create accessible modals with less code. If you, like me, had no idea this existed, then this post is for you.
Published June 7 2023
How many dropdown menus, dialogs, and modals have you built for a web app? And how many of them had the correct ARIA attributes, worked well for users who can’t see, could be closed by clicking outside or pressing escape, and were free of weird scroll behavior, glitchy transparent backgrounds, and other quirks? Well, it might be easier than you think.
Let’s create a small modal to illustrate the point. In addition to the markup,
there’s a tiny bit of JavaScript that toggles the display
property of the
.modal
and .backdrop
elements between "none"
and "block"
.
<div id="modal1" class="ex">
<button>Show me a modal!</button>
<div class="backdrop" style="display: none"></div>
<div class="modal" style="display: none">
<h2>Hi there!</h2>
<p>
Would you be interested in making some exciting choices about which third
parties we’ll leak your data to — accompanied by a heartwarming message about
how deeply and sincerely we care about your privacy?
</p>
<p>
<button>No, thank you</button>
</p>
</div>
</div>
CSS:
.backdrop {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.3);
}
.modal {
background: #fff;
padding: 40px;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
max-width: 500px;
width: 90vw;
max-height: 500px;
}
Beautiful. What more could one ask for? Well, a few things.
Try opening the modal and then press Tab to navigate around. Fortunately for me, this site is built sensibly enough that this exercise starts off promising: you tab through clickable elements in the modal. But then it goes downhill — Tab continues to navigate to links behind the modal. Not very modal, if you ask me.
When you’re done with the modal and want to close it, the next problem appears: clicking the backdrop has no effect, nor does pressing the Escape key. Building good modals is a lot of work. Your only option, unfortunately, is to click “No thanks.”
And if you’re unlucky enough to be reading this article with a screen reader, I don’t need to explain the other issues this solution introduces.
It turns out the browser has already solved most of these problems for us. Now let’s do what I love most about my job: solve problems by removing code.
The HTML is almost identical, except now the modal itself is a dialog
, not a
div
, and the div
that created the backdrop is completely gone:
<div id="modal2" class="ex">
<button>Vis meg en modal!</button>
<dialog>
<h2>Hei der!</h2>
<p>
Kan vi interessere deg i noen spennende valg om hvilke tredjeparter vi
skal lekke dataene dine til med en hjertevarmende tekst om hvor dypt og
inderlig vi bryr oss om personvernet ditt?
</p>
<p>
<button>Nei takk</button>
</p>
</dialog>
</div>
Uten at vi legger til noe CSS ser det sånn ut:
It won’t look super fancy, but it looks like a modal, and most importantly, it behaves exactly like a modal should: It sits above the rest of the content, the background is grayed out, keyboard focus is limited to the modal, the Escape key closes it, and screen readers get the correct information that this is a modal dialog. Not bad!
All this with a dialog
element and the following JavaScript:
var el = document.getElementById("modal2");
var modal = el.querySelector("dialog");
function toggle() {
if (modal.open) {
modal.close();
} else {
modal.showModal();
}
}
el.querySelectorAll("button")
.forEach(b => b.addEventListener("click", toggle));
To tighten up the visual look, we can add some CSS for the modal box itself. The backdrop is a pseudo-element and can be styled in any way you like:
.modal2 {
background: #fff;
padding: 40px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
max-width: 500px;
width: 90vw;
max-height: 500px;
}
.modal2::backdrop {
background: #591e1e66;
}
Nice! Notice the red-tinted backdrop.
Earlier I tempted you with the possibility to click the backdrop to close the modal, but that’s not the case yet. This is functionality we don’t get out of the box, but in classic W3C API style, we can achieve it, albeit in a somewhat clunky way.
Since the backdrop is a pseudo-element, it is unfortunately invisible to the
DOM. In the DOM’s eyes, the modal covers the entire screen when open. If we move
all padding from the modal itself to a div
inside it, then any click directly
on the dialog
element will be a click on the backdrop.
Updated HTML:
<div id="modal4" class="ex">
<button>Vis meg en modal!</button>
<dialog class="modal3">
<div class="modal3-content">
<h2>Hi there!</h2>
<p>
Would you be interested in making some exciting choices about which third
parties we’ll leak your data to — accompanied by a heartwarming message about
how deeply and sincerely we care about your privacy?
</p>
<p>
<button>No, thank you</button>
</p>
</div>
</dialog>
</div>
CSS:
.modal3 {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
max-width: 500px;
width: 90vw;
max-height: 500px;
}
.modal3-content {
background: #fff;
padding: 40px;
}
And JavaScript:
document
.querySelector("#modal4 dialog")
.addEventListener("click", function (e) {
if (e.target.tagName == "DIALOG") {
e.target.close();
}
});
Et voila!
These are just some of the things dialog
can do for you. It can also be used
for non-modal dialogs — check out MDN’s
reference for
more details.