Dialog

The native dialog element as a modal, opened by a button whose commandfor names it, with the page behind it inert and focus held inside.

Example

View Code
<button class="button" type="button" commandfor="delete-project" command="show-modal" data-variant="alert">Delete project</button>

<dialog class="dialog" id="delete-project" aria-labelledby="delete-project-title">
    <h2 id="delete-project-title">Delete this project?</h2>
    <p>Everything in it goes too, and this cannot be undone.</p>
    <footer>
        <form method="dialog"><button class="button" type="submit" data-emphasis="medium">Cancel</button></form>
        <button class="button" type="button" data-variant="alert">Delete</button>
    </footer>
</dialog>

When to use it

A decision that has to be made before anything else happens: confirming something destructive, a short form the page cannot carry, a message that must be acknowledged. Anything the reader can come back to later belongs on the page rather than over it.

How it works

A native dialog, opened as a modal. That one word is why the component exists in this shape: showModal makes everything behind the dialog inert, keeps focus inside it, closes it on Escape, and gives you a ::backdrop to paint. None of that is Yeti's, and none of it needs ARIA.

Opening is native too. A button carrying commandfor with the dialog's id and command="show-modal" opens it modally, with no script. Closing needs none either if you use a form: a button inside <form method="dialog"> closes the dialog on its own. dialog.js adds the two things the platform does not do yet: a click on the backdrop closes the dialog, and focus returns to the button that opened it when it closes.

<button class="button" type="button" commandfor="share" command="show-modal">Share</button>

<dialog class="dialog" id="share" data-max="sm" aria-labelledby="share-title">
    <h2 id="share-title">Share this page</h2>
    <p>Anyone with the link can read it.</p>
    <footer>
        <form method="dialog"><button class="button" type="submit">Done</button></form>
    </footer>
</dialog>

Accessibility

Name the dialog with aria-labelledby pointing at its heading, so it is announced as something rather than as an unnamed dialog. Because it is opened modally the page behind it is genuinely inert, not merely covered, so a screen reader cannot wander out of it. Focus returns to the opener on close, which is what keeps a keyboard reader's place.

Without the module the dialog still opens and Escape still closes it. What is lost is the backdrop click and, in Safari, the return of focus to the opener, because Safari does not focus a button when it is clicked. Load the module on any page where a keyboard reader will meet a dialog.

Attributes

Attribute Type Values Default Description
data-max enum 2xs, xs, sm, md, lg, xl, 2xl md The widest the dialog may grow, from the width scale; it is always narrower than the viewport by a gutter.

Children

  • > *: at least 1. The contents: a heading that names the dialog, then whatever it is for.
  • > footer: 0 to 1. The buttons, at the end edge. A button inside a form with method="dialog" closes it with no script.

Tokens

Token Description
--yeti-dialog-surface Background of the dialog.
--yeti-dialog-radius Corner of the dialog.
--yeti-dialog-padding Padding inside.
--yeti-dialog-backdrop The wash over the page behind it.
--yeti-shadow-md The dialog's shadow.
--yeti-space-md The least margin kept between the dialog and the viewport's edges.
--yeti-color-text The dialog's text.
--yeti-width-md The default maximum, when data-max is absent.
--yeti-space-sm Gap between the footer's actions.
--yeti-duration-fast How long the dialog and its backdrop take to appear.
--yeti-ease The curve of that transition.
Internal tokens (may change between minor versions) - `--_yeti-max`

Accessibility

  • Give the dialog a name with aria-labelledby pointing at its heading. A button with commandfor naming the dialog and command="show-modal" opens it modally: the browser makes everything behind it inert, holds focus inside, and closes on Escape; none of that needs ARIA or a script. A button inside a form with method="dialog" closes it without any script. dialog.js adds a backdrop click that closes the dialog and returns focus to the opening button on close; without it the dialog still opens and Escape still closes it.
Key Action
Escape Closes the dialog.
Tab Cycles inside the dialog, since the rest of the page is inert.

Browser support

  • Used without guards: invoker commands (commandfor and command), dialog, ::backdrop, @starting-style, transition-behavior: allow-discrete
  • Behind @supports: nothing

JavaScript

Optional enhancement: components/dialog/dialog.js. The component works without it.

Each event bubbles, crosses a shadow boundary, and cannot be cancelled.

Event Module Detail Description
yeti:open dialog.js none Dispatched on the dialog once the browser has opened it from a commandfor button.
yeti:close dialog.js none Dispatched on the dialog when it closes, however it was closed.

Available since 7.0.0.