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

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

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

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>

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.

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

AttributeTypeDefaultDescription
headlinestring | undefinedundefinedTitle of the dialog, and its accessible name. Leave it out for a dialog that brings its own heading, and pass aria-label instead.
headlineLevel1 | 2 | 3 | 4 | 5 | 62Heading level the headline renders as. Move it where your page outline needs it.
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.
dividerboolean | undefinedundefinedDraws a divider under the header. Useful when the content scrolls.
quickbooleanfalseOpens and closes without the fade transition.
elementHTMLElement | undefinedundefinedBindable reference to the popover element.