Dialogs
A dialog interrupts to ask for a decision or to show information that needs an answer before
anything else can happen. It is a native <dialog> in the top layer, so opening
it is a matter of pointing a trigger at it with command="show-modal" and commandfor; the browser handles Escape and the light dismiss.
Keep dialogs for choices that really 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"
divider
>
{#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 imperatively. 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
Anything you pass as children goes into a scrolling area between the supporting text and the actions, so the headline and the buttons stay in place while long content scrolls. The dialog itself never grows past the viewport.
<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>
Without animation
A dialog fades in over 250ms. Pass quick to skip that, for example when the dialog opens
as a direct answer to a keystroke and the delay would get in the way.
<Dialog
quick
headline="Rename file"
id="rename-dialog"
/>Accessibility
The surface renders role="dialog" with aria-modal="true". The headline labels it through aria-labelledby and the supportingText describes it
through aria-describedby, so both are announced when the dialog opens.
The headline is a level two heading, low enough to sit under the page's own h1. A
page that nests it deeper can say so with headlineLevel, which keeps the document
outline in order.
A dialog that brings its own heading can leave headline out and name itself with aria-label or aria-labelledby instead. Both land on the role="dialog" element rather than the popover around it, so the name reaches the screen
reader either way. The date pickers do exactly that.
While the dialog is open every other element on the page is marked inert, which keeps
both the keyboard and the screen reader cursor inside it. Focus moves to the dialog on open and
back to whatever was focused before on close. Escape and a click on the scrim close the dialog,
so always offer a cancelling action as well; a dialog that must not be dismissed by accident should
not rely on the scrim alone.
Theming
By default the dialog follows the theme without configuration: it draws on the surface and on-surface roles, uses secondary for the icon, on-surface-variant for the supporting text and scrim for the backdrop. Every
one of those defaults is reachable through a custom property when a dialog needs to depart from them.
| 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 | 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 - 3rem) | Height cap before the content scrolls. |
Setting the width to fit-content is worth knowing: the popover centres itself with margin: auto, so a dialog whose content is narrower than the container would
otherwise sit against the container's leading edge rather than in the middle of the screen.
<Dialog
headline="Rename file"
id="rename-dialog"
--np-dialog-container-width="24rem"
/>The date pickers use these to reshape the dialog completely. 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
Everything else you pass is forwarded to the popover element, so id, class, style and event handlers such as ontoggle work as expected.
| Attribute | Type | Default | Description |
|---|---|---|---|
headline | string | undefined | undefined | Title of the dialog, and its accessible name. Leave it out for a dialog that brings its own
heading, and pass aria-label instead. |
headlineLevel | 1 | 2 | 3 | 4 | 5 | 6 | 2 | Heading level the headline renders as. Move it where your page outline needs it. |
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. |
divider | boolean | undefined | undefined | Draws a divider under the header. Useful when the content scrolls. |
quick | boolean | false | Opens and closes without the fade transition. |
element | HTMLElement | undefined | undefined | Bindable reference to the popover element. |