Stop building inaccessible Modals (and how to fix them)
I found many inaccessible dialogs/modals across codebases. Mainly because the UI element is either implemented from scratch or from a third-party package; ARIA roles are sometimes nonexistent, or ARIA roles appear to be implemented differently in each codebase or third-party package — eek!
Note: I am not an accessibility guru, but implementing ARIA roles differently for a similar UI element across codebases is not ideal. This would cause assistive technologies, like screen readers, to read inconsistencies across applications.
Use semantic HTML tags wherever possible because they’re accessible by default. If you’re building a modal/dialog from scratch, you don’t need to anymore! Use the HTML5 semantic tag dialog
.
Inspecting a modal in the wild 🧐
The modal for mobile.twitter.com appears to be accessible but it’s built from scratch. I know this because I see a lot of div
tags.
A modal like this takes a lot of time an effort. ARIA roles need to be properly used. There need CSS and JavaScript to have it display and work properly. There is a finicky problem with using z-index
to manage the modal overlay. It’s inevitable that another DOM element will accidentally overthrow your modal one day… Managing z-indexes is not ideal — we will look at how the dialog tag solves this problem for us soon. The point is, it takes a lot of work to get it displaying and working properly. Let’s look at implementing a dialog/modal “from scratch” using the dialog tag.
Example
Let me show you how simple it is to create the dialog/modal from scratch!
Here we go… a very basic dialog:
<dialog open>I am a dialog!</dialog>
It might not be pretty, but it’s that simple and it’s accessible! Apply some CSS magic my friend and you got yourself a good looking dialog. 😎
Let’s look at how we can open/close a dialog with some JavaScript. Notice: because we’re handling the open/close via JavaScript, I removed the open attribute from the dialog tag.
HTML:
<dialog>
<p>I am a dialog!</p>
<button id="close-btn">close</button>
</dialog>
<button id="open-btn">open</button>
JavaScript:
const dialogEle = document.querySelector('dialog')document.getElementById('open-btn').addEventListener('click', () => {
dialogEle.show()
})document.getElementById('close-btn').addEventListener('click', () => {
dialogEle.close()
})
There we go, we can open and close our dialog! But wait — there is more; let’s take a look at how showModal()
works. It works similar to show()
but here’s what is different: 1) You can now click your ESC key to exit the modal. 2) You need to close the modal before interacting with other DOM elements that are outside the modal. 3) Your modal avoids z-index
collisions by pushing itself to a completely different “stacking context.” The stacking context is called the “Top Layer”, and this will prevent other DOM elements from “colliding”/overlaying your modal…
Conclusion
Whenever possible, use semantic HTML because it’s accessible by default and it works as intended. When using the semantic element dialog,
you can use showModal()
to push your modal onto the “Top Layer” stack. This prevents z-index
elements from potentially overlaying your pretty modal.
Note: There is also a Polyfill that allows you to support all modern and major browsers.
Please fork and play around with the codesandbox examples — Happy coding!
Things to not miss:
Hello! I’m Jon Major Condon. I spend my days as a Software Farmer, tending to client codebases at Bendyworks. As a farmer of software, I focus on anything web. That includes HTML, CSS, JavaScript, React, and so much more. That’s not all though; when I am not spending my time with my lovely wife and kids, I enjoy growing as a developer and helping others grow too. You can help me grow by correcting me if I ever have any misleading information in a blog. Thanks for reading!