DEV Community

Leo
Leo

Posted on • Originally published at news.html.to

Styling the dialog element: open is not the same as modal

The <dialog> element ships in a closed state. display: none by default, no visible box to style until you open it. That's the first thing that trips a hand-styled version: you're writing CSS for a component you can't see on the page yet.

Two methods open it, and they do not behave the same. show() opens a non-modal dialog: no backdrop, the page behind stays interactive, Esc does not close it. showModal() opens a modal one: it gets a backdrop, centers itself, listens for Esc, and makes the rest of the page inert. The CSS-Tricks piece by Geoff Graham pulls those apart before touching a single style, because the styling hooks are different too.

Three selectors, three different jobs

There are three ways to write "the dialog is open" in CSS, and they overlap without being interchangeable.

[open] is the attribute selector. It matches whenever the element carries the open attribute — both show() and showModal() set it.

:open is the pseudo-class version. The article notes Safari 26.5 just gained support for it.

:modal is the tighter one. The article calls out that it has higher specificity than :open. It's the wedge you reach for when a rule should apply only in the modal case — for example, a backdrop.

Pick the selector that says the thing you actually mean. [open] for any open state, :modal for the modal state, :open when you specifically want the pseudo-class semantics.

The backdrop lives in ::backdrop

Modal dialogs get a ::backdrop pseudo-element behind them in the top layer. That's the surface for the dim-out.

dialog::backdrop {
  background: rgb(0 0 0 / 0.5);
}
Enter fullscreen mode Exit fullscreen mode

Anything you'd apply to a normal element can live on it. The article reaches for transition on opacity for the fade, @starting-style so the fade actually plays on open instead of jumping in, and a blur() for the frosted look. It also demos shape() paths and view-transition-name as less obvious moves once you have the two states.

One override worth remembering: modal dialogs are auto-centered on the page. If you need to move that, margin-top (or any margin) still works.

Closing without JavaScript

The declarative close is the trick to keep in your pocket. A <form method="dialog"> treats its submit button as a close button. No event listener, no close() call.

<dialog>
  <form method="dialog">
    <button type="submit">Close</button>
  </form>
</dialog>
Enter fullscreen mode Exit fullscreen mode

Graham flags that he's not entirely sure about the semantic implications of this pattern, but it works and it's the shortest path to a closeable modal without script.

Where focus lands, and why that matters

Focus moves into the dialog when it opens. In the default case the close button is what gets focused, and the article calls out the risk directly: a stray Esc or Space press on that button can close the dialog by accident. If some other element inside the dialog should have initial focus, tabindex is the lever. Move it explicitly.

Focus trap and inert on the rest of the page are done for you — but only in the modal case. Non-modal dialogs leave the page interactive on purpose.

Scroll bleed on short modals

A short modal above a long page still lets wheel and touch scrolling reach the page behind. overscroll-behavior: contain keeps it contained. The article notes Chrome 144 extended support so it applies on non-scrollable scroll containers too, which is exactly the shape of a small modal.

The other API in the room

Invoker Commands — command on a button, commandfor pointing at the dialog — also get a mention, tagged in the article as "totally experimental." Worth reading, not worth shipping today.

The piece closes with a line from Zell Liew that's worth carrying: "Use Popover API for most popovers. Use Dialog's API only for modal dialogs." Two overlapping primitives, two different jobs. <dialog> earns its place when you specifically want a modal — the backdrop, the inertness, the Esc, the focus behavior. Everything lighter is what the popover API is for.

Top comments (0)