Modals are typical UI elements for many web apps. There are countless packages dedicated to that specific features, each framework creating its own variation. Popular frameworks like Bootstrap also have custom code to deal with it.
But all this feels really unnecessary when you could just use the dialog
tag, that also comes with features (like proper stacking, trapped focus, inert background…) that are hard (or impossible) to fully replicate using user land JavaScript.
But it’s not perfect
While dialog
is great, it’s not perfect either. For example, it’s impossible to use plain html to open the dialog. Most tutorials show you something along these lines:
<button id="modal">Open Modal dialog</button>
<dialog id="dialog">...</dialog>
<script>
const modal = document.getElementById('modal');
const dialog= document.getElementById('dialog');
modal.addEventListener('click', (event) => {
dialog.showModal();
});
</script>
And I feel really sad reading this. I would have imagined something like:
<button id="modal" data-dialog="dialog">Open Modal dialog</button>
<dialog id="dialog">...</dialog>
Wouldn’t that be better? Sure, it’s easy enough to add this as a global helper, but it should really be part of the spec. Adding a global helper is also not always so easy if the button and dialog are injected in the page, so you cannot rely on DOMContentLoaded event.
Don’t worry, I have a solution for you. But first, let’s talk about another problem.
What if you need old IOS support ?
Dialogs are only properly supported since IOS 15.4. So, if you want to support older browsers, that’s not so easy. Sure, there is a polyfill, but then it’s extra setup, and maybe you end up loading a polyfill just for that.
Wouldn’t it be great if the polyfill was loaded if needed, without any bloat for modern browsers.
Again, I have a solution for you.
Improving the dialog api
I recently (almost) finalized this demo. It uses under the hood the wonderful qsa-observer and a little bit of my own magic to do the work for you.
Basically, this will:
Check for dialog support, dynamically importing it from a cdn (or a chosen location) if needed, then, registering the polyfill.
Look for any dialog element, even if it’s injected in the page (thanks to qsa-observer… so if you load a dialog with htmx, it works!).
Bind buttons to open/close the dialog automatically and expose features (modal, dismissible) using data attributes.
Allows animation by adding a
is-closing
class, so that means it’s easy to add open/close animations (I found out it’s easier to use animations for this use case instead of transitions that are a bit tricky due to display:none being used).If you want to disable scrollbars, there is a little bit of magical css and css variables to avoid layout shifting.
While building this, I realized that creating offcanvas menus is very similar to opening dialogs. And indeed, with a couple of css styles, it’s easily achieved.
Once you start using this approach (improving native html), you might wonder: why stop to dialogs ? And indeed, that’s exactly what I thought. This is why I’m currently building this and see how far it gets. Maybe I’ll talk more about it in the next article.