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 popover in the top layer, so opening it is a matter of pointing a trigger at it with popovertarget; 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">
	let dialog: ReturnType<typeof Dialog> | undefined = $state()
</script>

<Button popovertarget="simple-dialog">Show Dialog</Button>
<Dialog
	bind:this={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
			onclick={() => {
				dialog?.hidePopover()
			}}
			variant="text">Cancel</Button
		>
		<Button
			onclick={() => {
				dialog?.hidePopover()
			}}
			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
showPopover() => voidShows the dialog.
hidePopover() => 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.

<Dialog headline="Terms of service" id="terms" divider>
	<p>...</p>
	{#snippet actions()}
		<Button variant="text" onclick={() => dialog?.hidePopover()}>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.

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

The dialog has no tokens of its own. 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, so it follows the theme without further configuration.

The width is a plain CSS property: the container is 37rem wide, at least 19.5rem, and never wider than the viewport. Override it with style or a class when a dialog needs to be narrower or wider.

<Dialog
	headline="Rename file"
	id="rename-dialog"
	style="width:24rem"
/>

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
headlinestringRequired. Title of the dialog, and its accessible name.
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.