Dialogs
A dialog asks for a decision or shows information that needs an answer before the user can go on.
It is a native <dialog> in the top layer. To open it, point a trigger at it
with command="show-modal" and commandfor. The browser handles Escape and light dismiss.
Use dialogs only for choices that cannot wait. For a message that only confirms what happened, use a snackbar instead.
Usage
<script lang="ts">
import { Button, Dialog } from 'noph-ui'
import { Icon } from 'noph-ui/icons'
</script>
<Button command="show-modal" commandfor="simple-dialog">Show dialog</Button>
<Dialog
headline="Reset settings?"
supportingText="This will reset your app preferences back to their default settings."
id="simple-dialog"
>
{#snippet icon()}
<Icon>settings</Icon>
{/snippet}
{#snippet actions()}
<Button command="close" commandfor="simple-dialog" variant="text">Cancel</Button>
<Button command="close" commandfor="simple-dialog" variant="text">Accept</Button>
{/snippet}
</Dialog>
Methods
Bind a reference to the dialog with bind:this to call its methods. Type the reference
with ReturnType<typeof Dialog>. It is undefined until the
component has mounted, so call through ?..
| Method | Type | Description |
|---|---|---|
show | () => void | Shows the dialog. |
close | () => void | Hides the dialog. |
Scrollable content
The supporting text and the children go into a scrolling area between the headline and the
actions. The headline and the buttons stay in place while long content scrolls. The dialog never
grows past the viewport. divider draws a line above and below the scrolling area.
<script lang="ts">
import { Button, Dialog } from 'noph-ui'
const paragraphs = Array.from({ length: 12 }, (_, index) => index + 1)
</script>
<Button command="show-modal" commandfor="scrolling-dialog">Show terms</Button>
<Dialog
headline="Terms of service"
id="scrolling-dialog"
divider
--np-dialog-container-width="32rem"
>
{#each paragraphs as paragraph (paragraph)}
<p>
Paragraph {paragraph}. Consecutive paragraphs make the body taller than the dialog, which is
what puts the scroll bar on the content instead of on the page.
</p>
{/each}
{#snippet actions()}
<Button command="close" commandfor="scrolling-dialog" variant="text">Close</Button>
{/snippet}
</Dialog>
Full-screen
variant="full-screen" is for tasks with several steps or form fields on a compact
window. It fills a window narrower than 600px, without rounded corners or scrim. On a larger
window it floats, at most 560dp wide. Its 56dp header holds a close button (named by closeLabel), the headline and the actions. The actions are usually one
confirming text button such as Save. actionBar adds a 56dp bar along the bottom.
<script lang="ts">
import { Button, Dialog, TextField } from 'noph-ui'
</script>
<Button command="show-modal" commandfor="event-dialog">New event</Button>
<Dialog variant="full-screen" headline="New event" id="event-dialog">
{#snippet actions()}
<Button variant="text" command="close" commandfor="event-dialog">Save</Button>
{/snippet}
<div style="display:grid;gap:1rem">
<TextField label="Event name" />
<TextField label="Location" />
</div>
</Dialog>
Dismissing
By default Escape and a click on the scrim close the dialog. A dialog that asks for a
decision can take closedby="closerequest", so only Escape closes it, or closedby="none", so only its own actions do.
<Dialog
closedby="closerequest"
headline="Discard draft?"
id="discard-dialog"
/>Without animation
A dialog fades in on --np-motion-expressive-slow-effects. It closes like a menu, with
a short fade on --np-motion-expressive-default-effects while it shrinks a little.
Pass quick to skip both, for example when the dialog opens in direct response to a keystroke.
<Dialog
quick
headline="Rename file"
id="rename-dialog"
/>Accessibility
A basic dialog renders role="alertdialog", as M3 asks for the web. A full-screen one
renders role="dialog". Pass role="dialog" to a basic dialog that holds a
task, such as a form, instead of an alert. The date and time pickers do this. The headline labels the dialog through aria-labelledby, and the supportingText describes it through aria-describedby. Both are announced
when the dialog opens.
The headline is a level two heading, so it fits under the page's h1. If your page
nests it deeper, set headlineLevel to keep the document outline in order.
A dialog with its own heading can leave out headline and use aria-label or aria-labelledby instead. Both are set on the dialog element.
The date pickers do this.
While the dialog is open, every other element on the page is inert. This keeps the
keyboard and the screen reader cursor inside the dialog. On open, focus moves to the first
interactive element in the dialog, or to an element with autofocus. On close, focus
goes back to the element that had it before. Escape and a click on the scrim close the
dialog unless closedby says otherwise. Always offer a cancel action as well.
Theming
The dialog uses the theme's surface-container-high and on-surface roles, secondary for the icon, on-surface-variant for the supporting text and scrim for the backdrop. You can change each default with a custom property.
| Property | Default | Affects |
|---|---|---|
--np-dialog-container-width | 37rem | Dialog width. fit-content shrinks it to its contents. |
--np-dialog-container-min-width | 19.5rem | Lower bound on that width. |
--np-dialog-inset | 2rem 1rem | Space kept between the dialog and the viewport edge. |
--np-dialog-padding | 1.5rem | Padding inside the surface. 0 for edge-to-edge content. |
--np-dialog-container-color | --np-color-surface-container-high | Surface colour. |
--np-dialog-container-shape | --np-shape-corner-extra-large | Corner radius. |
--np-dialog-elevation | --np-elevation-3 | Shadow. none for a flat, full-screen surface. |
--np-dialog-max-height | calc(100dvh - 4rem) | Height cap before the content scrolls. |
Use fit-content when the content is narrower than the container. The dialog centres
itself with margin: auto, so without it the content sits against the container's
leading edge instead of in the middle of the screen.
<Dialog
headline="Rename file"
id="rename-dialog"
--np-dialog-container-width="24rem"
/>The date pickers use these properties to change the dialog. The modal picker sizes itself to its
content with no padding. The range picker becomes a flat, full-screen surface with --np-dialog-inset: 0 and --np-dialog-elevation: none.
API
Attributes
All other attributes go to the popover element, including id, class, style and event handlers such as ontoggle.
| Attribute | Type | Default | Description |
|---|---|---|---|
headline | string | undefined | undefined | Title of the dialog, and its accessible name. Leave it out for a dialog with its own
heading, and pass aria-label instead. |
headlineLevel | 1 | 2 | 3 | 4 | 5 | 6 | 2 | Heading level of the headline. Change it to fit your page outline. |
supportingText | string | undefined | undefined | Explanatory line below the headline. It becomes the dialog's description. |
icon | Snippet | undefined | undefined | Icon above the headline. Adding one also centers the headline. |
actions | Snippet | undefined | undefined | Buttons at the bottom of the dialog, aligned to the end. In a full-screen dialog they sit at the end of the header. |
divider | boolean | undefined | undefined | Draws dividers above and below the scrolling content, or under a full-screen header. |
variant | 'basic' | 'full-screen' | 'basic' | A basic dialog floats over a scrim. A full-screen one fills a compact window. |
closeLabel | string | 'Close' | Names the close button of a full-screen dialog. |
actionBar | Snippet | undefined | undefined | The 56dp bar along the bottom of a full-screen dialog. |
closedby | 'any' | 'closerequest' | 'none' | 'any' | What closes the dialog: the scrim and Escape, only Escape, or neither. |
role | 'alertdialog' | 'dialog' | 'alertdialog', full-screen 'dialog' | The dialog's role. |
quick | boolean | false | Opens and closes without the transitions. |
element | HTMLElement | undefined | undefined | Bindable reference to the popover element. |