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

Reset settings?

This will reset your app preferences back to their default settings.

<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 ?..

MethodTypeDescription
show() => voidShows the dialog.
close() => voidHides 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.

Terms of service

Paragraph 1. Consecutive paragraphs make the body taller than the dialog, which is what puts the scroll bar on the content instead of on the page.

Paragraph 2. Consecutive paragraphs make the body taller than the dialog, which is what puts the scroll bar on the content instead of on the page.

Paragraph 3. Consecutive paragraphs make the body taller than the dialog, which is what puts the scroll bar on the content instead of on the page.

Paragraph 4. Consecutive paragraphs make the body taller than the dialog, which is what puts the scroll bar on the content instead of on the page.

Paragraph 5. Consecutive paragraphs make the body taller than the dialog, which is what puts the scroll bar on the content instead of on the page.

Paragraph 6. Consecutive paragraphs make the body taller than the dialog, which is what puts the scroll bar on the content instead of on the page.

Paragraph 7. Consecutive paragraphs make the body taller than the dialog, which is what puts the scroll bar on the content instead of on the page.

Paragraph 8. Consecutive paragraphs make the body taller than the dialog, which is what puts the scroll bar on the content instead of on the page.

Paragraph 9. Consecutive paragraphs make the body taller than the dialog, which is what puts the scroll bar on the content instead of on the page.

Paragraph 10. Consecutive paragraphs make the body taller than the dialog, which is what puts the scroll bar on the content instead of on the page.

Paragraph 11. Consecutive paragraphs make the body taller than the dialog, which is what puts the scroll bar on the content instead of on the page.

Paragraph 12. Consecutive paragraphs make the body taller than the dialog, which is what puts the scroll bar on the content instead of on the page.

<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.

New event

<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.

PropertyDefaultAffects
--np-dialog-container-width37remDialog width. fit-content shrinks it to its contents.
--np-dialog-container-min-width19.5remLower bound on that width.
--np-dialog-inset2rem 1remSpace kept between the dialog and the viewport edge.
--np-dialog-padding1.5remPadding inside the surface. 0 for edge-to-edge content.
--np-dialog-container-color--np-color-surface-container-highSurface colour.
--np-dialog-container-shape--np-shape-corner-extra-largeCorner radius.
--np-dialog-elevation--np-elevation-3Shadow. none for a flat, full-screen surface.
--np-dialog-max-heightcalc(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.

AttributeTypeDefaultDescription
headlinestring | undefinedundefinedTitle of the dialog, and its accessible name. Leave it out for a dialog with its own heading, and pass aria-label instead.
headlineLevel1 | 2 | 3 | 4 | 5 | 62Heading level of the headline. Change it to fit your page outline.
supportingTextstring | undefinedundefinedExplanatory line below the headline. It becomes the dialog's description.
iconSnippet | undefinedundefinedIcon above the headline. Adding one also centers the headline.
actionsSnippet | undefinedundefinedButtons at the bottom of the dialog, aligned to the end. In a full-screen dialog they sit at the end of the header.
dividerboolean | undefinedundefinedDraws 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.
closeLabelstring'Close'Names the close button of a full-screen dialog.
actionBarSnippet | undefinedundefinedThe 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.
quickbooleanfalseOpens and closes without the transitions.
elementHTMLElement | undefinedundefinedBindable reference to the popover element.