unscripted/ui

Dialog

A modal with entry/exit animations, backdrop blur, light dismiss, focus trapping and Esc-to-close. Zero JavaScript — the button opens it declaratively.

dialogexit animations

How it works

  • Native <dialog> gives you the top layer, focus trapping, inert background and Esc-to-close for free.
  • commandfor + command="show-modal" on the trigger opens it declaratively. This invoker commands API replaces the dialog.showModal() call you’d otherwise write in JavaScript.
  • closedby="any" adds light dismiss: clicking the backdrop closes it. If a stray click must not dismiss it (destructive confirms), use the Alert Dialog instead.
  • Entry animation: starting:open:* (that’s @starting-style) defines where the dialog animates from. Exit animation: the base classes are the closed state, and transition-discrete (transition-behavior: allow-discrete) keeps display alive until the transition finishes.
  • The backdrop animates the same way via backdrop:, including a backdrop-blur.
  • The one thing native modals don’t give you is a scroll lock: the page behind is inert but still scrolls. overscroll-none on the dialog plus backdrop:overflow-hidden backdrop:overscroll-contain on the backdrop keep the scroll from chaining out — none rather than contain so the dialog’s own scroll doesn’t rubber-band at its edges either. Needs Chrome 144+ (where overscroll-behavior starts applying to non-scrollable containers); everywhere else the background scrolls, nothing breaks.
  • Tailwind v4’s preflight zeroes all margins, which removes the browser’s built-in dialog centering. m-auto puts it back so keep it.

Accessibility

Everything the JS dialog libraries reimplement (focus trap, aria-modal, inert page behind, focus restore on close) is the browser’s native behavior here. The one thing <dialog> does not supply is an accessible name: point aria-labelledby at the title (and aria-describedby at the body text) like the demo does, so screen readers announce what the dialog is asking.

Browser support

Minimum stable version per engine, resolved at build time from MDN's browser-compat-data; Baseline status from the official web-features dataset. Everything degrades gracefully — the “when missing” column is the actual behavior, not a broken page.

FeatureChromeEdgeFirefoxSafariWhen missing
<dialog> elementBaseline · widely available37799815.4Baseline, widely available. Modal behavior, focus handling and Esc-to-close are native.
Invoker commands (commandfor / command)Baseline 2025 · newly available13513514426.2Buttons with commandfor do nothing. Until Baseline reaches your audience, open the dialog with a 3-line shim or from your framework.
closedby="any" (light dismiss)Limited availability134134141Esc still closes the dialog (platform default); clicking the backdrop does not.
Entry/exit animations (allow-discrete + @starting-style)Baseline 2024 · newly available11711712917.5Elements appear and disappear instantly. Nothing breaks — you just lose the animation.

† @supports cannot test at-rules, so the badge checks transition-behavior: allow-discrete, which co-shipped with @starting-style in every engine.

‡ HTML feature — @supports can only test CSS, and we ship no JS to detect it, so it gets no live badge.