I was on the epicgames.com website the other day, signing up so I could relive my Magic: The Gathering glory days with Arena. While doing that I saw their style for modal dialogs and thought I should try to re-create that with because apparently I’m both of those types of nerd.

This thing came up on top of other content, so that alone makes it appropriate for the HTML element. We’ll use that.
No Taller than Viewport
It’s not absolutely required that the entire needs to be shorter than the viewport. If it’s taller, you just need to be able to scroll to all of the content it contains. The default styling for
allows for that.
But I would argue that if you’re putting actions that relate to the content of the dialog at the bottom then you should limit the height of the dialog to the viewport so that those actions are always visible. If a dialog simply has an ✕ close button on the top, maybe it doesn’t matter, but here we’ve got important buttons at the bottom, so it does.
The default styling for dialog includes position: absolute;
and we can keep that while limiting the height like:
dialog {
block-size: 90dvb;
inset-block-start: 5dvb;
}
Code language: CSS (css)
That will limit the height to essentially 90% of the viewport height (the dv
b part means “dynamic viewport size in the block direction”). I like the “dynamic” sizing units because it means that it accommodates browser “chrome” (toolbars and stuff) being present (or not). The inset amount is half of what’s left over, so essentially vertical centering.

Note that the dialog element’s default styles can be a bit confusing and you need to understand when you can override safely and when you can’t without doing extra work. Simon Willison has an interesting article on this: Styling an HTML dialog modal to take the full height of the viewport.
Limited Width and Centering
This example has lots of written content in it (a bunch of s) so it’s best practice to limit the width to a readable line length. When that’s the intent, it’s nice to use the
ch
unit as it maps roughly to number of characters, which is what we’re trying to limit.
dialog {
...
inline-size: min(50ch, 90dvi);
margin-inline: auto;
}
Code language: CSS (css)
Fifty characters of width is providing good readability here, but it’s possible that the current screen is actually narrower than that, hence the min()
function assuring that the width will never be wider than 90% of the viewport. I’m not sure if our fancy dancy “dynamic viewport units in the inline direction” is buying us anything here, but it balances the usage with where we were using dvb
).


open
attribute)
Modal vs Non Modal (and the This seems like a pretty important distinction to know about:
- “Modal” (
dialog.showModal()
) means interrupt everything else, this dialog needs to be dealt with immediately.- The ESC key automatically works to close it.
- Focus is put on the first focusable element within the dialog
- Focus is trapped within the dialog
- “Non Modal” (
dialog.show()
) means the dialog is just there, but doesn’t require exclusive or immediate action.- None of those other things above happen. You likely want to bind the ESC key yourself anyway.
- When you use the open attribute (useful when working on them!) like
the dialog is open non-modally.
In our example, where a person needs to accept-or-not the Terms & Conditions, it’s likely modal is the better approach. That way what the person is trying to do can only continue if they accept or take a different path if they do not. This choice is likely required to know what to do next.
A non-modal dialog implementation might be something like a “site navigation drawer” where some of the attributes of using a modal is desirable (e.g. the hide/show behavior) but focus trapping is not required or even desirable.
Here’s a video of focus trapping at work with the modal state. Notice the “focusable element” (an anchor link) never gets focus, because it’s not within the .
No Invokers?
There is no way to show a dialog in the modal state from HTML alone. Not by default nor by button click. The .showModal()
function must be used. For that reason, it may feel like it would be an advantage to try to use popovers instead, which do have HTML-only controls, aka “invokers”. Unfortunately, if you go with a popover, you don’t get the focus trapping that you can get with a modal dialog, which is ultra useful (see video above).
To bone up: What’s the Difference Between HTML’s Dialog Element and Popovers?
Maybe someday we can get something like:
<button dialogTarget="my-dialog">
Toggle Dialog in Non-Modal State
button>
<button dialogTargetModal="my-dialog">
Toggle Dialog in Modal State
button>
Code language: HTML, XML (xml)
display
value
Careful with the The reason that is invisible by default is simply that default styles render it with
display: none;
. That is precariously easy to override. In fact, in this very demo I was playing with, I wanted to use display: flex;
on the dialog to have the header/content/footer look where the content is flex: 1;
to push the header and footer away and take up the remaining space. But you’ll have problems like this:
dialog {
display: flex;
}
Code language: CSS (css)
It’s probably most resilient to just not mess with the display
value of dialogs, instead using some internal wrapper element instead. But I’m a gamblin’ man apparently so I did:
dialog {
&[open] {
display: flex;
}
}
margin
can come anytime now
Trimming Any time I slap a bunch of elements into a container (read: doing web design) I’m reminded that the block-direction margins are kind of annoying in that context. The last item, particularly if it’s content, will likely have margin at the end that pushes further than you want it away from the container, or the start, or both.
It leads to this kind of thing:
.container {
:first-child {
margin-block-start: 0;
}
:last-child {
margin-block-end: 0;
}
}
When instead we could be living in the future like:
.container {
margin-trim: block;
}
Code language: CSS (css)
I once said this and I’m sticking to it:
If you add
padding
in the main flow direction of an element, addingmargin-trim
in that same direction.
Right aligned buttons deux façons
I had item-flow
on my brain when I was tinkering with this and thinking about how flow directions can be reversed, which is something I don’t think about or use very much. For some reason when I needed to right-align those buttons for “Accept” and “Close”, my fingers went for:
dialog {
> footer {
display: flex;
flex-direction: row-reverse;
}
}
I’m not going to recommend that, as it changes the tabbing order awkwardly for no great reason. You should probably just do:
dialog {
> footer {
display: flex;
justify-content: end;
}
}
But, ya know, always nice to have options. You could also not even bother with flex
and do text-align: end
or even old school float: right
the buttons.
Autofocus
In reading over the MDN for dialogs, this stood out to me as something I didn’t know:
The
autofocus
attribute should be added to the element the user is expected to interact with immediately upon opening a modal dialog. If no other element involves more immediate interaction, it is recommended to addautofocus
to the close button inside the dialog, or the dialog itself if the user is expected to click/activate it to dismiss.
They didn’t mince words there and it makes sense to me, so I put it on the “Accept” button as that seems like the most likely user action.
<dialog>
...
<footer>
...
<button autofocus>Acceptbutton>
footer>
dialog>
Code language: HTML, XML (xml)
Feel free to peak at the demo to see a few other thing like color modes and a backdrop. Sometimes fairly simple looking HTML elements have quite a bit of detail to implementation!