Exploring The Dialog Element In HTML
At the tippy top of my "Just in Case" learning for 2026, I wanted to take a look at the <dialog> element in HTML. The <dialog> has been "Widely Available" since 2024; but, I haven't had a reason to use it. As much as InVision was heavily modals-based, we had an established pattern that I wasn't about to abandon in the final death throes of the company. Which means, this is my first earnest look at this element.
Run this demo in my JavaScript Demos project on GitHub.
View this code in my JavaScript Demos project on GitHub.
This post is here to build-up my own muscle memory, so I won't go into too much detail. But, I'll say that the dialog element is the browser's native implementation of what we've been hand-coding on the web for years. Which is to say, it's super exciting because the native implementation is more robust than anything I've hand crafted, especially with regard to accessibility and keyboard access.
A <dialog> element opens in the "top layer" of the browser. Which means that it immediately side-steps a whole host of z-index issues. And when opened as a "modal", it marks all external content as "inert". Which means that the content below the dialog layer is removed from the accessibility tree and can neither receive mouse clicks nor keyboard interactions. This alone makes the native dialog a superior option to anything I've ever coded.
One deviation that the native <dialog> makes from my hand-crafted solutions is that there's no "wrapper" element for the dialog to help with scrolling large amounts of content. Meaning, if your modal dialog has more content than can fit on the screen (which is likely an anti-pattern anyway), the overflow-scrollbar renders within the dialog element. Meaning, when there's overflow, you're not scrolling the dialog within the document, you're scrolling the content within the dialog.
With that said, here's a little demo code (again, mostly for myself). I have two buttons: one button opens the dialog in a non-modal state; meaning, the rest of the document can still be manipulated. The other button opens the dialog in a modal state; meaning, the rest of the document becomes inert.
The dialog itself contains a form with "Yes" and "No" buttons. The action of the form is set to dialog; which means that submitting the form will automatically close the dialog and assign the value of the submission button to the .returnValue property of the dialog.
Aside: when the dialog is closed, focus is automatically returned to the previously-active element. Yet another part of the functionality that will never have to be hand-coded again!
<!doctype html>
<html lang="en">
<body>
<h1>
Exploring The Dialog Element In HTML
</h1>
<p>
<button type="button" class="triggerModal">
Toggle Modal
</button>
<button type="button" class="triggerNonModal">
Toggle Non-Modal
</button>
</p>
<!--
Adding large spacer to see how dialogs affect document flow; and how we can
lock-down scrollbar while modal is open.
-->
<hr style="margin-bottom: 100rem;" >
<!-- [closedby=any] allows the click-outside (on backdrop) to close the modal. -->
<dialog closedby="any">
<!--
[method=dialog] on the form will close the modal and set returnValue to the
value of the submit button used to process the form. Note that form field
values are left in place unless explicitly reset.
-->
<form method="dialog">
<p>
Are you sure?
</p>
<p>
<button type="submit" value="Yes">
Yes
</button>
<button type="submit" value="No">
No
</button>
</p>
</form>
</dialog>
<style type="text/css">
/* When a modal window is open, remove scroll from body. */
:is( html, body ):has( :modal ) {
overflow: hidden ;
}
/* Note: backdrop only shows for MODAL experiences (not non-modal). */
dialog::backdrop {
backdrop-filter: blur( 1px ) ; /* Not widely available yet. */
background-color: #99999966;
}
dialog {
border: 2px solid #000000 ;
border-radius: 4px ;
scrollbar-gutter: stable ; /* Not widely available yet. */
}
/* We can use [open] DOM state to animate-in the modal using CSS keyframes. */
dialog[ open ] {
animation-duration: 200ms ;
animation-fill-mode: forwards ;
animation-iteration-count: 1 ;
animation-name: dialogEnter ;
animation-timing-function: ease-out ;
}
@keyframes dialogEnter {
from {
opacity: 0.7 ;
scale: 0.7 ;
}
to {
opacity: 1.0 ;
scale: 1.0 ;
}
}
</style>
<script type="text/javascript">
var dialog = document.querySelector( "dialog" );
var toggleModal = document.querySelector( ".triggerModal" );
var toggleNonModal = document.querySelector( ".triggerNonModal" );
// Listen for the close event.
dialog.onclose = ( event ) => {
console.log(
`Closed with return value: %c${ dialog.returnValue }`,
`font-weight: bold ; color: red ;`
);
};
// Listen for the cancel event (closed via ESC key or background click).
dialog.oncancel = ( event ) => {
console.log( "Modal canceled" );
};
// Wire up the MODAL toggle button.
toggleModal.onclick = ( event ) => {
// Trying to open an already-open modal throws an error. As such, let's make
// sure it's closed before we open the modal version. If it's already closed,
// closing it again is a safe no-op.
dialog.close();
dialog.showModal();
};
// Wire up the NON-MODAL toggle button.
toggleNonModal.onclick = ( event ) => {
if ( dialog.open ) {
dialog.close();
} else {
dialog.show();
}
};
</script>
</body>
</html>
One feature that I forgot to explore in this demo is the fact the .close() method can take an optional argument which will become the .returnValue property of the dialog. The returnValue property has to be a string; so that removes the ability to return "complex objects". But I think it could still be useful.
I'm excited to finally have the <dialog> tool in my tool-belt. To be honest, I'm trying to use fewer dialogs in my work; but, when and where it makes sense, it's great to know that I have simple, robust, native browser solution to reach for.
Want to use code from this post? Check out the license.
Reader Comments
Post A Comment — ❤️ I'd Love To Hear From You! ❤️
Post a Comment →