Put the opening Button in the trigger slot and the task in the default slot. label and description become the visible title and description and name the dialog for assistive technology. The Dialog moves focus inside on open, keeps the rest of the page inert and returns focus to the trigger on close.
Use Alert Dialog instead when the user must confirm a consequential decision.
Footer actions
The first element in the close slot closes the dialog when activated. Anything in footer is ordinary content with no close behavior, which suits secondary actions such as copying a link that should keep the dialog open. For other placement, call dialog.registerCloseAction(button) and retain the returned cleanup function. Empty footers are hidden.
Footer and close
Default density
Compact density
Restart from the initial state
Show the copyable source
The corner Close stays visible unless another named close action is reachable, so a dialog can always be dismissed.
Modality
modality="non-modal" leaves the page interactive and scrollable behind the dialog. Use it for reference content, such as a shortcut list, that people read while they keep working.
Non-modal
Default density
Compact density
Restart from the initial state
Show the copyable source
modality="container" makes the dialog modal within one region instead of the whole page. Assign that region to the container property: the dialog renders inside it, makes only the region’s other content inert and leaves the rest of the page interactive. Give the region position: relative and isolation: isolate.
While open, the dialog:
renders inside the container instead of the native top layer, so it is clipped to the container and stays visible while the container is fullscreen;
makes only the container’s other content inert; content outside the container stays interactive;
traps Tab focus within the dialog; like document modality it never pulls back focus that moves elsewhere, so overlays another owner adds (such as autofill menus) stay usable;
does not lock page scroll;
sizes its Overlay to the container and centers and bounds Content within it;
handles Escape and close requests only while focus is inside the container, and uses the usual initial and final focus. Final focus is not restored when the user has already moved focus outside the container;
exposes aria-modal="false", because the rest of the document remains available. The container’s inert siblings carry the isolation.
Outside presses within the container hit the Overlay and follow closeOnOutsideInteraction; presses outside the container never dismiss.
Container modality
Default density
Compact density
Restart from the initial state
Show the copyable source
Invoice INV-2048Simulate timeoutSign in again
The rest of the page stays usable while the dialog is open.
Invoice INV-2048Simulate timeoutSign in again
The rest of the page stays usable while the dialog is open.
<section id="dialog-demo-region" aria-label="Invoice editor" style="position:relative;isolation:isolate;overflow:clip;display:grid;gap:var(--tp-space-3);min-block-size:16rem;padding:var(--tp-space-5);border:var(--tp-border-width) solid var(--tp-border);border-radius:var(--tp-radius-xl);max-inline-size:32rem;margin-inline:auto;inline-size:100%">
<strong>Invoice INV-2048</strong>
<tp-field label="Billing contact">
<tp-input default-value="Maya Chen"></tp-input>
</tp-field>
<tp-dialog id="dialog-demo-contained" modality="container" label="Session expired" description="Sign in again to keep editing this invoice.">
<tp-button slot="trigger" variant="outline" style="justify-self:start">Simulate timeout</tp-button>
<tp-button slot="close">Sign in again</tp-button>
</tp-dialog>
</section>
<p style="text-align:center;color:var(--tp-muted-foreground);font-size:var(--tp-text-sm)">The rest of the page stays usable while the dialog is open.</p>
<script type="module">
await customElements.whenDefined('tp-dialog');
document.querySelector('#dialog-demo-contained').container = document.querySelector('#dialog-demo-region');
</script>
Without a usable container the dialog falls back to page-wide modal. Drawer does not support container. Alert Dialog supports modal and container.
Backdrop
backdrop="blur" keeps the dark fill and blurs the page behind it, which suits a focused task over a busy screen. It changes only the appearance; modality still decides whether there is a backdrop at all. Drawer and Alert Dialog accept the same attribute.
Blurred backdrop
Default density
Compact density
Restart from the initial state
Show the copyable source
ShareDone
ShareDone
<tp-dialog backdrop="blur" label="Share “Q3 planning”" description="Anyone with the link can view.">
<tp-button slot="trigger" variant="outline">Share</tp-button>
<tp-field label="Link">
<tp-input readonly default-value="https://acme.com/d/q3-planning"></tp-input>
</tp-field>
<tp-button slot="close">Done</tp-button>
</tp-dialog>
Detached triggers
When the opening control lives elsewhere, such as a table row or a menu, register it with registerTrigger(). It toggles the dialog and receives focus on close, just like a slotted trigger.
Registered trigger
Default density
Compact density
Restart from the initial state
Show the copyable source
tp-open-change is cancelable. Prevent it to keep the dialog open, for example while a draft still has unsaved text.
Veto closing
Default density
Compact density
Restart from the initial state
Show the copyable source
Write a commentPost comment
Write a commentPost comment
<tp-dialog id="dialog-demo-draft" label="New comment" description="Visible to everyone with access to this document.">
<tp-button slot="trigger" variant="outline">Write a comment</tp-button>
<tp-field label="Comment">
<tp-text-area name="comment" rows="3" default-value="Looks good, one note on the pricing table."></tp-text-area>
</tp-field>
<tp-button slot="close">Post comment</tp-button>
</tp-dialog>
<script type="module">
const dialog = document.querySelector('#dialog-demo-draft');
dialog.addEventListener('tp-open-change', (event) => {
const draft = dialog.querySelector('tp-text-area').value;
const dismissing = ['escape-key', 'outside-press'].includes(event.detail.reason);
if (!event.detail.value && dismissing && draft) event.preventDefault();
});
</script>
Here the close action still posts, while Escape and outside presses are refused until the text area is empty.
Anatomy and presentation
Section spacing comes from one variable. Set --tp-dialog-spacing (a length, normally a --tp-space-* role; default --tp-space-4) on the Dialog, or on Content through its part hook, to change the header, body and footer inset together. --tp-dialog-spacing: 0 gives an edge-to-edge body for compositions that bring their own layout, such as a settings dialog with a navigation panel. A visible corner Close without a header still keeps its own row. Drawer and Alert Dialog read the same variable.
Default motion runs on opening only. Accepted closing removes the surface and
backdrop immediately, without waiting for an exit transition. A consumer may
explicitly claim the backdrop exit through tp-motion-request to provide its
own animation and completion. The portal wrapper contributes no layout box
beside the trigger.
API Reference
Every element also accepts motion-policy.
Attributes
AttributePropertyTypeDefaultDescriptionopenopenboolean—Whether the dialog is open; set it to open or close from code without a proposal.default-opendefaultOpenbooleanfalseOpens the dialog when it first connects, when open is not set.keep-mountedkeepMountedbooleanfalseKeeps the content in the document after closing, hidden.close-on-escapecloseOnEscapebooleantrueWhether Escape proposes closing.modalitymodalityDialogModality—Requested modality. container scopes modality to container; when that cannot apply, the
document modality is used with a diagnostic. Values a family member does not support
normalize to modal.close-on-outside-interactioncloseOnOutsideInteractionboolean—Whether a press or focus outside the dialog proposes closing.show-close-controlshowCloseControlbooleantrueWhether the corner Close button always shows; when false it still shows unless another close action is reachable.portalportalbooleanfalseRenders the dialog into a container at the end of the document, out of clipping or stacking ancestors.portal-identifierportalIdentifierstring | undefined—Identifier of a shared portal container, for dialogs that should render into the same one.show-headershowHeaderbooleantrueWhether the header with the title and the description shows; hidden, they still name the dialog.show-footershowFooterbooleantrueWhether the footer shows.initial-focusinitialFocusDialogInitialFocus'first'Where focus goes on open: the first control, the popup itself, an element or a selector, or a resolver that receives how the dialog was opened.labellabelstring''The visible title, which also names the dialog for assistive technology.descriptiondescriptionstring''The visible description, announced with the title.force-renderforceRenderbooleanfalseDraws its own backdrop when opened from another dialog, which otherwise shows only its parent's.backdropbackdrop'dark' | 'blur''dark'Overlay treatment: a dark fill, or the same fill blurring what it covers.trigger-identifiertriggerIdentifierstring | undefined—Identifier of the registered trigger the dialog belongs to while open; it opens only for that trigger.default-trigger-identifierdefaultTriggerIdentifierstring | undefined—Identifier of the trigger a dialog opened by default-open belongs to.
Properties
PropertyTypeDefaultDescriptionfinalFocusDialogFinalFocus'trigger'Where focus returns on close: the trigger, the previous focus, an element, a resolver, or false to leave it.containerOwnedPortalContainernullThe element the dialog renders into: the portal container, or the region that container modality scopes to.onOpenChange((event: TpSurfaceOpenChangeEvent) => void) | undefined—Called with each open or close proposal before tp-open-change is dispatched.onOpenChangeComplete((open: boolean) => void) | undefined—Called after the dialog finishes opening or closing.handleDialogHandle | undefined—A handle from createDialogHandle() that opens the dialog from triggers registered elsewhere, with a payload each.actionsread-onlyobject{ close: () => this.close(), unmount: () => this.unmount() }The close and unmount actions, for code that should not hold the dialog itself.effectiveModalityread-onlyDialogModality—The modality currently applied after resolving container (see modality).payloadread-onlyunknown—The payload of the trigger or handle call that opened the dialog.activeTriggerIdentifierread-onlystring | undefined—Identifier of the trigger that opened the dialog.
Events
EventTypeDescriptiontp-open-changeTpSurfaceOpenChangeEventCancelable proposal to open or close, before the dialog changes; detail.value is the proposed state and detail.reason what asked (trigger-press, close-action, escape-key, outside-press, imperative-action and others). On a close, detail.preventUnmountOnClose() keeps the content mounted.tp-open-change-completeCustomEvent<{ open: boolean; }>After the dialog finishes opening or closing; detail.open.
Methods
MethodReturnsDescriptionregisterTrigger(element, options)() => voidMakes an element elsewhere in the document a trigger: it opens the dialog and receives focus on close. Returns a cleanup function.setOpen(open, reason, sourceEvent?)voidProposes opening or closing, with the reason the proposal carries.close()voidProposes closing, as a close action would.unmount()voidRemoves content kept mounted after closing, closing first when open.registerCloseAction(element)() => voidBind a consumer-owned, named action wherever it is composed in this surface.
Slots
SlotDescriptiondefaultThe body of the task.triggerThe control that opens the dialog, usually a Button.titleThe title, in place of label.descriptionThe description, in place of description.footerFooter content with no close behavior, such as a secondary action.closeAn action that closes the dialog when pressed, after its own handlers.
CSS parts
PartDescription::part(dialog)The host around the trigger and the dialog.
Related
Components, widgets and patterns that use this one.