Creating Nested Modals with HTML’s <dialog> Element
For a long time, creating website modals — those popups that are often used for confirmation alerts, file browsers, and newsletter signup forms — required implementing a complex mixture of HTML, CSS, and JS, often in the form of a plugin or library like jQuery UI.
Back in 2014, though, Google introduced the <dialog> element, which promised to simplify creating modals. But it wasn’t until 2022 that support for the <dialog> element became standardized across all major browsers. That level of browser support, combined with its built-in accessibility features, makes the <dialog> element a very viable alternative to other modal approaches.
How simple is it to use the HTML <dialog>? This simple:
<button id="modalOpen">Open Modal</button>
<dialog class="modal" id="modal">
<h1>This is my modal's title</h1>
<p>This is my modal's content.</p>
<button id="modalClose">Close Modal</button>
</dialog>
<script>
// Set modal variables
const modal = document.getElementById('modal');
const modalOpen = document.getElementById('modalOpen');
const modalClose = document.getElementById('modalClose');
// Open modal when the 'Open Modal' button is clicked
modalOpen.addEventListener("click", () => {
modal.showModal();
});
// Close modal when the 'Close Modal' button is clicked
modalClose.addEventListener("click", () => {
modal.close();
});
</script>See an example in action. It’s definitely not the prettiest modal you’ve ever seen, but it gets the job done. And all that’s really necessary for it is just a relatively small amount of HTML and vanilla JS, plus any CSS used to make it look better.
Sometimes, however, you need more than just one modal. Sometimes, you need to launch a modal from within another modal. Consider these scenarios:
- A CMS employs a modal that lets users browse their image library in order to select a previously uploaded image for use in a blog post. The “image browser” modal also contains an “Upload” button that, when clicked, launches another modal that contains the UI for uploading new images.
- A “delete confirmation” modal displays a second “Are you really, really sure you want to delete that?” confirmation modal for particularly sensitive deletions.
- A modal is used to display a list of items (e.g., website users, blog posts) Clicking on an item’s title launches a second modal that lets you edit and save changes to the item.
Fortunately, the <dialog> element handles nested modals like a champ. As you might guess, the code for nested modals isn’t all that dissimilar from the code for a single modal:
<button id="modalOpen">Open Modal</button>
<dialog class="modal" id="modal">
<h1>This is the first modal's title</h1>
<p>This is the first modal's content.</p>
<button id="modalTwoOpen">Open Second Modal</button>
<button id="modalClose">Close</button>
</dialog>
<script>
// Set modal variables
const modal = document.getElementById('modal');
const modalOpen = document.getElementById('modalOpen');
const modalClose = document.getElementById('modalClose');
// Open modal when the 'Open Modal' button is clicked
modalOpen.addEventListener("click", () => {
modal.showModal();
});
// Close modal when the 'Close Modal' button is clicked
modalClose.addEventListener("click", () => {
modal.close();
});
</script>
<dialog class="modal" id="modalTwo">
<h1>This is the second modal's title</h1>
<p>This is the second modal's content.</p>
<button id="modalTwoClose">Close</button>
</dialog>
<script>
// Set modal variables
const modalTwo = document.getElementById('modalTwo');
const modalTwoOpen = document.getElementById('modalTwoOpen');
const modalTwoClose = document.getElementById('modalTwoClose');
// Open modal when the 'Open Modal' button is clicked
modalTwoOpen.addEventListener("click", () => {
modalTwo.showModal();
});
// Close modal when the 'Close Modal' button is clicked
modalTwoClose.addEventListener("click", () => {
modalTwo.close();
});
</script>See the nested modals in action. Each <dialog> modal has its own JavaScript that sets its respective variables and handles its opening and closing. The only major difference is that the second modal’s “Open” button is located inside the first modal — which makes sense since the second modal can’t be displayed unless the first modal is already visible.
But before you start nesting all of your modals, keep in mind that nested modals can be tricky, UX-wise. Too many modals within modals might get increasingly convoluted for users to navigate, making it difficult for them to remember where they are or understand what they can/should do next. This cognitive burden should be reduced as much as possible. Thus, before you start implementing nested modals, take another look at your UI’s workflow and ask yourself if it really needs them, or if they can be removed altogether.
Also, I did encounter one accessibility issue in Safari regarding keyboard navigation. Although Chrome and Firefox trap focus correctly while tabbing (i.e., you can only tab to elements within the second modal), older versions of Safari allow you to tab to the first modal’s elements, as well, including its “Close” button. This would allow you to close the first modal while the second modal is still visible, leaving it just hanging there in space — which doesn’t make sense since the second modal’s context is derived from the first modal. Additionally, VoiceOver in Safari doesn’t announce the first modal’s elements as you tab through them, potentially adding to the confusion.
Those issues aside, the <dialog> element remains a really simple way to add fully customizable — and accessible — modals to your website, nested or otherwise.