Cmpnts

Menu

Organisms

Dialog

A modal window on top of the page, for a decision, a short form or a drawer of settings.


Watering rota

Bed 3 is yours this week. Water it in the evening.

Code
HTML
<button type="button" class="button" commandfor="default-dialog" command="show-modal"><span>Open dialog</span></button>

<dialog id="default-dialog" class="dialog" aria-labelledby="default-dialog-title">
  <article class="card">
    <header class="card__header">
      <h2 id="default-dialog-title" class="card__title heading">Watering rota</h2>
      <button type="button" class="button button--plain button--icon button--sm" commandfor="default-dialog" command="close">
        <span class="sr-only">Close</span>
        <svg class="icon" width="1em" height="1em" aria-hidden="true" focusable="false"><use href="#Close"/></svg>
      </button>
    </header>
    <div class="card__body">
      <p class="text">Bed 3 is yours this week. Water it in the evening.</p>
    </div>
  </article>
</dialog>

Why it exists

A Dialog stops someone mid-task, which is a big ask, so it should be saved for what can’t wait or can’t happen on the page.

It is the native <dialog>, opened as a modal. The browser then moves focus in, returns it on close, handles Escape, puts the Dialog above everything and makes the rest of the page inert. Hand-built modals have to recreate all of that, and that’s where they tend to fail people. So the library adds no script and no focus trap.

The Dialog is only the shell: placement, backdrop and motion. The surface is a Card, so what’s inside is built the way it is everywhere else.

When to use

  • To interrupt for a decision the page can’t go on without, such as a confirmation.
  • For a short, focused task that keeps the page behind it, such as renaming something.
  • As a drawer for settings, details or navigation on a small screen.
  • Not for a non-modal floating panel: that is a Popover.
  • Not for a passing message: that is a Toast or a Banner.

Rules

  • Modal only. Open it with command="show-modal" or showModal(). A non-modal floating panel is a .popover, not a Dialog opened with show().
  • Closing.
    • The header’s close Button is .button.button--plain.button--icon with a hidden label and commandfor="{id}" command="close".
    • Footer actions are submit Buttons in a form method="dialog"; the chosen Button’s value becomes the Dialog’s returnValue.
    • In a Form Dialog the Card is the Form, and Cancel is a close command, so Enter in a Field submits rather than cancels.
    • Escape closes it (native).
    • closedby="any" adds light dismiss (a click on the backdrop) where supported; elsewhere Escape and the Buttons still work. Leave it off a Dialog holding unsaved input.
  • Focus.
    • The first focusable element takes focus, or the one marked autofocus: the first field in a Form Dialog, the safe action in a destructive confirmation.
    • Focus returns to the opener on close (native).
  • Semantics.
    • Name it with aria-labelledby.
    • A confirmation that interrupts to ask for a decision takes role="alertdialog" and aria-describedby on its message.
    • No aria-modal: showModal() makes the rest of the page inert.
    • aria-haspopup="dialog" on the opener is optional.
  • Scrolling bodies are focusable and named.

Keyboard and screen readers

The browser provides all of this for a modal <dialog>. There’s nothing to script.

  • Opening moves focus into the Dialog: to the first focusable element, or the one marked autofocus.
  • Tab moves through the Dialog’s controls. The page behind is inert, so it can’t be reached.
  • Escape closes it, and focus returns to the control that opened it.
  • A screen reader announces it as a dialog with the name from aria-labelledby, and can’t read the inert page behind it. A confirmation also reads its message, through aria-describedby.

Confirmation

Delete plot A-14?

Its sowing records and rota weeks go too. This can't be undone.

Code
HTML
<button type="button" class="button" commandfor="delete-dialog" command="show-modal"><span>Delete plot</span></button>

<dialog id="delete-dialog" class="dialog" role="alertdialog" aria-labelledby="delete-title" aria-describedby="delete-message" style="--dialog-width: 28rem;">
  <article class="card">
    <header class="card__header">
      <h3 id="delete-title" class="card__title heading">Delete plot A-14?</h3>
    </header>
    <div class="card__body">
      <p id="delete-message" class="text">Its sowing records and rota weeks go too. This can't be undone.</p>
    </div>
    <footer class="card__footer justify-end">
      <form method="dialog" class="button-group">
        <button type="submit" value="cancel" class="button" autofocus><span>Keep plot</span></button>
        <button type="submit" value="delete" class="button button--strong button--danger">
          <svg class="icon" width="1em" height="1em" aria-hidden="true" focusable="false"><use href="#Delete"/></svg>
          <span>Delete plot</span>
        </button>
      </form>
    </footer>
  </article>
</dialog>

Form dialog

Rename plot

Code
HTML
<button type="button" class="button" commandfor="rename-dialog" command="show-modal"><span>Rename plot</span></button>

<dialog id="rename-dialog" class="dialog" aria-labelledby="rename-title">
  <form method="dialog" class="card">
    <header class="card__header">
      <h3 id="rename-title" class="card__title heading">Rename plot</h3>
      <button type="button" class="button button--plain button--icon button--sm" commandfor="rename-dialog" command="close">
        <span class="sr-only">Close</span>
        <svg class="icon" width="1em" height="1em" aria-hidden="true" focusable="false"><use href="#Close"/></svg>
      </button>
    </header>
    <div class="card__body">
      <div class="field">
        <label for="plot-name" class="field__label label">Plot name</label>
        <input id="plot-name" name="name" class="input" value="A-14" required autofocus />
      </div>
    </div>
    <footer class="card__footer justify-end">
      <div class="button-group">
        <button type="button" class="button" commandfor="rename-dialog" command="close"><span>Cancel</span></button>
        <button type="submit" value="save" class="button button--strong button--accent"><span>Save</span></button>
      </div>
    </footer>
  </form>
</dialog>

Drawers

Settings

Reminders go out on Sunday evenings.

Code
HTML
<!-- Settings: inline end -->
<button type="button" class="button" commandfor="settings-drawer" command="show-modal"><span>Settings</span></button>

<dialog id="settings-drawer" class="dialog dialog--drawer" aria-labelledby="settings-title">
  <article class="card">
    <header class="card__header">
      <h3 id="settings-title" class="card__title heading">Settings</h3>
      <button type="button" class="button button--plain button--icon button--sm" commandfor="settings-drawer" command="close">
        <span class="sr-only">Close</span>
        <svg class="icon" width="1em" height="1em" aria-hidden="true" focusable="false"><use href="#Close"/></svg>
      </button>
    </header>
    <div class="card__body card__body--scrollable" tabindex="0" aria-label="Settings">
      <p class="text">Reminders go out on Sunday evenings.</p>
    </div>
  </article>
</dialog>

<!-- Navigation: inline start, opened by a menu button -->
<button type="button" class="button button--plain button--icon button--sm" commandfor="nav-drawer" command="show-modal" aria-haspopup="dialog">
  <span class="sr-only">Open allotment navigation</span>
  <svg class="icon" width="1em" height="1em" aria-hidden="true" focusable="false"><use href="#Navigation"/></svg>
</button>

<dialog id="nav-drawer" class="dialog dialog--drawer dialog--start" aria-labelledby="nav-title">
  <article class="card">
    <header class="card__header">
      <h3 id="nav-title" class="card__title heading">Allotment</h3>
      <button type="button" class="button button--plain button--icon button--sm" commandfor="nav-drawer" command="close">
        <span class="sr-only">Close</span>
        <svg class="icon" width="1em" height="1em" aria-hidden="true" focusable="false"><use href="#Close"/></svg>
      </button>
    </header>
    <div class="card__body card__body--scrollable">
      <nav class="navigation-tree" aria-labelledby="nav-title">
        <ul role="list">
          <li><a href="/plots" class="button" aria-current="page"><span>Plots</span></a></li>
          <li><a href="/rota" class="button"><span>Rota</span></a></li>
        </ul>
      </nav>
    </div>
  </article>
</dialog>

Do and Don’t

DoDon’t
Open it with command="show-modal" or showModal().Open a .dialog with show(); use a .popover.
Name it with aria-labelledby.Add aria-modal; the rest of the page is already inert.
Put autofocus on the safe action in a destructive confirmation.Focus the destructive Button first.
Make Cancel a close command in a Form Dialog.Make Cancel a submit Button in a Form that Enter submits.
Leave closedby="any" off a Dialog holding unsaved input.Let a stray click throw away someone’s typing.
Use .dialog--drawer.dialog--start for navigation.Build a drawer from a div and a script.

In Storybook

See also

  • Card: A surface with named parts, such as a header, a body and a footer, for one thing a person can read, compare or act on.
  • Popover: A layer anchored to the control that opens it, holding a Card or a Menu.
  • Button: An action on the page, such as saving a Form, opening a Dialog or changing a setting.
  • Field, Fieldset and Form: What goes around form controls, such as labels, hints, errors, groups of choices and the Form's actions.
  • Sidebar: A side panel beside the main content, for section navigation, filters or tools.

Search the docs