Skip to content

Dialog

A modal panel built on the native <dialog>, rendered as <dialog is="root-dialog">.

Usage

Move to archive

The project stays searchable and can be restored at any time.

Archiving hides the project from the sidebar for everyone on the team.

You can undo this from the archive.

Move to archive

The project stays searchable and can be restored at any time.

Archiving hides the project from the sidebar for everyone on the team.

You can undo this from the archive.

<button is="root-button" type="button" data-variant="primary" data-color="default" command="show-modal" commandfor="dialog-usage">
  Open dialog
</button>

<dialog is="root-dialog" id="dialog-usage" aria-labelledby="dialog-usage-title">
  <header>
    <h2 id="dialog-usage-title">Move to archive</h2>
    <button is="root-button" type="button" data-variant="tertiary" data-color="neutral" square command="close" commandfor="dialog-usage" aria-label="Close">
      <i is="root-icon" data-icon="x"></i>
    </button>
    <p>The project stays searchable and can be restored at any time.</p>
  </header>
  <section data-part="content">
    <p>Archiving hides the project from the sidebar for everyone on the team.</p>
  </section>
  <footer>
    <p>You can undo this from the archive.</p>
    <div data-part="actions">
      <button is="root-button" type="button" data-variant="secondary" data-color="default" command="close" commandfor="dialog-usage">Cancel</button>
      <button is="root-button" type="button" data-variant="primary" data-color="default" command="close" commandfor="dialog-usage">Archive</button>
    </div>
  </footer>
</dialog>

Three bands, and they are the document's own elements: a <header>, the body marked data-part="content" - "everything between the two" is not a selector - and a <footer>. Nothing is built by the component: a dialog is a container, and what goes in it is the page's. The header's grid puts the close button beside the title and the description under both, so the three are siblings in the markup and need no wrappers.

command / commandfor are the platform's invoker commands: the button opens and the buttons inside close it, with no script of your own. Where they are not supported the dialog stays closed rather than opening un-dismissable, so pair them with the native API - dialog.showModal() / dialog.close() - if you have to support that.

Sizes

data-size takes xs, small, medium and full; medium is the default and needs no value written. Measured on the design: 400, 600 and 900px, and full is 1024x768 - the design's own full-screen modal, which stops growing rather than filling the viewport. Every step is a maximum: below it the dialog shrinks with the screen, so a 900px dialog on a 600px one is a 600px dialog and never a horizontal scroll.

Extra small

400px, for a single question.

Small

600px, for a short form.

Medium

900px, the default, for content with a shape of its own.

Full screen

1024x768, for a task that takes over the page.

Extra small

400px, for a single question.

Small

600px, for a short form.

Medium

900px, the default, for content with a shape of its own.

Full screen

1024x768, for a task that takes over the page.

<button is="root-button" type="button" data-variant="secondary" data-color="default" command="show-modal" commandfor="dialog-xs">Open xs</button>
<button is="root-button" type="button" data-variant="secondary" data-color="default" command="show-modal" commandfor="dialog-small">Open small</button>
<button is="root-button" type="button" data-variant="secondary" data-color="default" command="show-modal" commandfor="dialog-medium">Open medium</button>
<button is="root-button" type="button" data-variant="secondary" data-color="default" command="show-modal" commandfor="dialog-full">Open full</button>

<dialog is="root-dialog" id="dialog-xs" data-size="xs" aria-labelledby="dialog-xs-title">
  <header>
    <h2 id="dialog-xs-title">Extra small</h2>
    <button is="root-button" type="button" data-variant="tertiary" data-color="neutral" square command="close" commandfor="dialog-xs" aria-label="Close">
      <i is="root-icon" data-icon="x"></i>
    </button>
  </header>
  <section data-part="content"><p>400px, for a single question.</p></section>
</dialog>

<dialog is="root-dialog" id="dialog-small" data-size="small" aria-labelledby="dialog-small-title">
  <header>
    <h2 id="dialog-small-title">Small</h2>
    <button is="root-button" type="button" data-variant="tertiary" data-color="neutral" square command="close" commandfor="dialog-small" aria-label="Close">
      <i is="root-icon" data-icon="x"></i>
    </button>
  </header>
  <section data-part="content"><p>600px, for a short form.</p></section>
</dialog>

<dialog is="root-dialog" id="dialog-medium" aria-labelledby="dialog-medium-title">
  <header>
    <h2 id="dialog-medium-title">Medium</h2>
    <button is="root-button" type="button" data-variant="tertiary" data-color="neutral" square command="close" commandfor="dialog-medium" aria-label="Close">
      <i is="root-icon" data-icon="x"></i>
    </button>
  </header>
  <section data-part="content"><p>900px, the default, for content with a shape of its own.</p></section>
</dialog>

<dialog is="root-dialog" id="dialog-full" data-size="full" aria-labelledby="dialog-full-title">
  <header>
    <h2 id="dialog-full-title">Full screen</h2>
    <button is="root-button" type="button" data-variant="tertiary" data-color="neutral" square command="close" commandfor="dialog-full" aria-label="Close">
      <i is="root-icon" data-icon="x"></i>
    </button>
  </header>
  <section data-part="content"><p>1024x768, for a task that takes over the page.</p></section>
  <footer>
    <div data-part="actions">
      <button is="root-button" type="button" data-variant="primary" data-color="default" command="close" commandfor="dialog-full">Done</button>
    </div>
  </footer>
</dialog>

Bottom sheet

data-variant="sheet" is the mobile declension the design draws: docked to the bottom edge, full width, rounded on the top corners only. Three things change with it - a 1px Neutral 300 border instead of the elevation, because a sheet touching the edge of the screen has nothing to cast a shadow onto; the two bands dropping their grey so header, body and footer read as one surface; and the buttons stacking full width, which is what a thumb can hit.

It is declared rather than inferred from the viewport. TODO: check with design - whether the switch should happen on its own below a breakpoint is still open.

Share this project

Anyone with the link can read it.

Sharing sends an email to everyone you pick.

Share this project

Anyone with the link can read it.

Sharing sends an email to everyone you pick.

<button is="root-button" type="button" data-variant="primary" data-color="default" command="show-modal" commandfor="dialog-sheet">
  Open the sheet
</button>

<dialog is="root-dialog" id="dialog-sheet" data-variant="sheet" aria-labelledby="dialog-sheet-title">
  <header>
    <h2 id="dialog-sheet-title">Share this project</h2>
    <button is="root-button" type="button" data-variant="secondary" data-color="default" square command="close" commandfor="dialog-sheet" aria-label="Close">
      <i is="root-icon" data-icon="x"></i>
    </button>
    <p>Anyone with the link can read it.</p>
  </header>
  <section data-part="content">
    <p>Sharing sends an email to everyone you pick.</p>
  </section>
  <footer>
    <div data-part="actions">
      <button is="root-button" type="button" data-variant="primary" data-color="default" command="close" commandfor="dialog-sheet">Copy link</button>
      <button is="root-button" type="button" data-variant="secondary" data-color="default" command="close" commandfor="dialog-sheet">Invite people</button>
      <button is="root-button" type="button" data-variant="tertiary" data-color="default" command="close" commandfor="dialog-sheet">Not now</button>
    </div>
  </footer>
</dialog>

Confirmation

role="alertdialog" is for a choice that cannot be taken back. It keeps the backdrop click from closing it - dismissal has to be a deliberate button - and it is the one case where the panel should not carry a close button either.

Delete this project?

Everything in it goes with it, for everyone.

This cannot be undone.

Delete this project?

Everything in it goes with it, for everyone.

This cannot be undone.

<button is="root-button" type="button" data-variant="primary" data-color="danger" command="show-modal" commandfor="dialog-delete">
  Delete the project
</button>

<dialog is="root-dialog" id="dialog-delete" data-size="xs" role="alertdialog" aria-labelledby="dialog-delete-title">
  <header>
    <h2 id="dialog-delete-title">Delete this project?</h2>
    <p>Everything in it goes with it, for everyone.</p>
  </header>
  <section data-part="content">
    <p>This cannot be undone.</p>
  </section>
  <footer>
    <div data-part="actions">
      <button is="root-button" type="button" data-variant="secondary" data-color="default" command="close" commandfor="dialog-delete">Keep it</button>
      <button is="root-button" type="button" data-variant="primary" data-color="danger" command="close" commandfor="dialog-delete">Delete</button>
    </div>
  </footer>
</dialog>

Attributes

Attribute Type Default Description
open boolean false Dialog visibility (native)
data-size 'xs' | 'small' | 'medium' | 'full' 'medium' Maximum width, and a height for full
data-variant 'sheet' - The bottom-sheet declension
role 'alertdialog' 'dialog' A choice that cannot be taken back: no backdrop dismissal
aria-labelledby string - Points at the title; use aria-label when there is no visible one

Parts

Part Element Description
header <header> Title, close button and description
content <section> The body, and the only part that scrolls
footer <footer> Description and [data-part="actions"]
actions [data-part="actions"] The buttons, at the end of the line - stacked in a sheet

Events

Event Detail Description
root-dialog.toggle { state: 'open' | 'close' } Fired when the open attribute changes

Accessibility

Native <dialog> already gives you initial focus movement, an inert background, Escape-to-close, and focus restoration on close - do not add a manual JS focus trap on top; it is redundant and blocks the platform's own escape hatches. The one thing the platform does not do is close on a backdrop click, so this component adds it - except for role="alertdialog", as above. Page scroll is locked for as long as any modal <dialog> is open (foundations/_reset.css). See this writeup for the reasoning.

Extends

HTMLDialogElement - use with <dialog is="root-dialog">.