Dialog
Dialogs are modal windows that interrupt the user to convey critical information or request a decision.
Usage
forge-dialog shows modal (or non-modal) content through open, mode, type, preset, placement, fullscreen, and persistent, and typically composes with a forge-scaffold’s header/body/footer slots for structure. Use it sparingly, only for critical information or a decision that truly needs to block the rest of the app. For the common confirm/cancel prompt, reach for the pre-built forge-confirmation-dialog instead of hand-building one. For lower-priority feedback, use a toast, banner, or inline message instead.
Rules
Structure
A dialog’s content is typically a forge-scaffold with a header toolbar (title plus a close icon button), a body (the message or form), and a footer toolbar (actions) — pinning the header and footer so only the body scrolls. The title goes in the header toolbar’s start slot and the close icon button in its end slot. The footer toolbar sets inverted so its divider sits above it.
<forge-dialog labelledby="edit-profile-title">
<forge-scaffold>
<forge-toolbar slot="header">
<h2 slot="start" id="edit-profile-title" class="forge-typography--heading3">Edit profile</h2>
<forge-icon-button slot="end" density="medium" aria-label="Close dialog">
<forge-icon name="close"></forge-icon>
</forge-icon-button>
</forge-toolbar>
<div slot="body" class="p-medium">...</div>
<forge-toolbar slot="footer" inverted>
<div slot="end" class="flex items-center gap-medium">
<forge-button variant="tonal">Cancel</forge-button>
<forge-button variant="filled">Save</forge-button>
</div>
</forge-toolbar>
</forge-scaffold>
</forge-dialog>
A dialog built from a scaffold's header, body, and footer slots.
<div>
<forge-button id="open-dialog-button-demo" variant="raised">
Open dialog
</forge-button>
<forge-dialog id="dialog-demo" label="Title text" description="Lorem ipsum dolor sit amet consectetur adipisicing elit.">
<forge-scaffold class="h-auto [--forge-scaffold-width:500px]">
<forge-toolbar slot="header">
<h1 class="forge-typography--heading4 m-0" slot="start">
Title text
</h1>
<forge-icon-button id="close-dialog-icon-button-demo" slot="end" aria-label="Close dialog">
<forge-icon name="close">
</forge-icon>
</forge-icon-button>
</forge-toolbar>
<p slot="body" class="p-medium">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Fugit sed pariatur error repellendus eos! Quas, optio esse ad illum quis blanditiis rerum quia.
Corrupti, ad hic velit praesentium voluptatum dolores?
</p>
<forge-toolbar slot="footer" inverted>
<forge-button id="close-dialog-button-demo" slot="end" variant="raised">
Close
</forge-button>
</forge-toolbar>
</forge-scaffold>
</forge-dialog>
</div>
<script type="module">
import { IconRegistry } from "@tylertech/forge/icon";
import { tylIconClose } from "@tylertech/tyler-icons";
IconRegistry.define([tylIconClose]);
const openButton = document.getElementById("open-dialog-button-demo");
const dialog = document.getElementById("dialog-demo");
const closeIconButton = document.getElementById("close-dialog-icon-button-demo");
const closeButton = document.getElementById("close-dialog-button-demo");
openButton?.addEventListener("click", () => dialog.open = true);
closeIconButton?.addEventListener("click", () => dialog.open = false);
closeButton?.addEventListener("click", () => dialog.open = false);
dialog?.addEventListener("forge-dialog-close", () => dialog.open = false);
</script>Opening a dialog
Set open on a dialog already in the page to show it, and remove it (or set open = false) to close it. To create one on demand, build it in script, append it to document.body, then set open:
const dialog = document.createElement("forge-dialog");
dialog.label = "Edit profile";
dialog.append(content);
document.body.append(dialog);
dialog.open = true;
In Angular apps, open dialogs through DialogService from @tylertech/forge-angular instead of creating the element yourself.
Modes
mode controls how a dialog sits relative to the rest of the page. modal (the default) renders above everything with a backdrop and makes the rest of the page inert. nonmodal renders in the normal page flow, above other content, with no backdrop and nothing made inert. inline-modal is the middle ground — a backdrop is shown, but unlike modal, the rest of the page is not made inert.
Prevent close
Listen for forge-dialog-before-close and call event.preventDefault() to block a close — useful for validating input or waiting on an async save before letting the dialog close. This event only fires for light-dismiss actions (clicking the backdrop or pressing Escape), not for an explicit close triggered from your own button.
dialogEl.addEventListener("forge-dialog-before-close", (event) => {
if (!formIsValid) {
event.preventDefault();
}
});
Confirmation dialog
For the common confirm/cancel prompt, use the pre-built forge-confirmation-dialog instead of hand-building a scaffold — it handles the title, message, and button slots for you.
A pre-built confirmation dialog handles the title, message, and button slots.
<div>
<forge-button id="open-confirmation-dialog-demo" variant="raised">
Delete record
</forge-button>
<forge-confirmation-dialog id="confirmation-dialog-demo" label="Delete record?">
<span slot="title">
Delete record?
</span>
<span slot="message">
This action can't be undone.
</span>
<span slot="secondary-button-text">
Cancel
</span>
<span slot="primary-button-text">
Delete
</span>
</forge-confirmation-dialog>
</div>
<script type="module">
import { ToastComponent } from "@tylertech/forge/toast";
const openButton = document.getElementById("open-confirmation-dialog-demo");
const confirmationDialog = document.getElementById("confirmation-dialog-demo");
openButton?.addEventListener("click", () => confirmationDialog.open = true);
confirmationDialog?.addEventListener("forge-confirmation-dialog-action", (event) => {
const { value } = event.detail;
ToastComponent.present({ message: value ? "Record deleted." : "Delete canceled." });
});
</script>Listen for forge-confirmation-dialog-action to handle the result. detail.value is true when the user clicks the primary button, and false when they click the secondary button or press Escape (detail.reason is 'light-dismiss'). The dialog closes on its own afterward. To keep it open while an async action runs, call preventDefault() and set isBusy, then close it yourself when the action finishes.
confirmationDialog.addEventListener("forge-confirmation-dialog-action", async (event) => {
if (!event.detail.value) {
return;
}
event.preventDefault();
confirmationDialog.isBusy = true;
await deleteRecord();
confirmationDialog.open = false;
});
Presets
Set preset to bottom-sheet, top-sheet, left-sheet, or right-sheet to slide the dialog in from an edge instead of the default, centered dialog preset — common on mobile.
Bottom sheet
A dialog sliding in from the bottom of the screen.
<div>
<forge-button id="open-dialog-button-bottom-sheet" variant="raised">
Open dialog
</forge-button>
<forge-dialog id="dialog-bottom-sheet" label="Title text" description="Lorem ipsum dolor sit amet consectetur adipisicing elit." preset="bottom-sheet">
<forge-scaffold class="h-auto [--forge-scaffold-width:500px]">
<forge-toolbar slot="header">
<h1 class="forge-typography--heading4 m-0" slot="start">
Title text
</h1>
<forge-icon-button id="close-dialog-icon-button-bottom-sheet" slot="end" aria-label="Close dialog">
<forge-icon name="close">
</forge-icon>
</forge-icon-button>
</forge-toolbar>
<p slot="body" class="p-medium">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Fugit sed pariatur error repellendus eos! Quas, optio esse ad illum quis blanditiis rerum quia.
Corrupti, ad hic velit praesentium voluptatum dolores?
</p>
<forge-toolbar slot="footer" inverted>
<forge-button id="close-dialog-button-bottom-sheet" slot="end" variant="raised">
Close
</forge-button>
</forge-toolbar>
</forge-scaffold>
</forge-dialog>
</div>
<script type="module">
import { IconRegistry } from "@tylertech/forge/icon";
import { tylIconClose } from "@tylertech/tyler-icons";
IconRegistry.define([tylIconClose]);
const openButton = document.getElementById("open-dialog-button-bottom-sheet");
const dialog = document.getElementById("dialog-bottom-sheet");
const closeIconButton = document.getElementById("close-dialog-icon-button-bottom-sheet");
const closeButton = document.getElementById("close-dialog-button-bottom-sheet");
openButton?.addEventListener("click", () => dialog.open = true);
closeIconButton?.addEventListener("click", () => dialog.open = false);
closeButton?.addEventListener("click", () => dialog.open = false);
dialog?.addEventListener("forge-dialog-close", () => dialog.open = false);
</script>Top sheet
A dialog sliding in from the top of the screen.
<div>
<forge-button id="open-dialog-button-top-sheet" variant="raised">
Open dialog
</forge-button>
<forge-dialog id="dialog-top-sheet" label="Title text" description="Lorem ipsum dolor sit amet consectetur adipisicing elit." preset="top-sheet">
<forge-scaffold class="h-auto [--forge-scaffold-width:500px]">
<forge-toolbar slot="header">
<h1 class="forge-typography--heading4 m-0" slot="start">
Title text
</h1>
<forge-icon-button id="close-dialog-icon-button-top-sheet" slot="end" aria-label="Close dialog">
<forge-icon name="close">
</forge-icon>
</forge-icon-button>
</forge-toolbar>
<p slot="body" class="p-medium">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Fugit sed pariatur error repellendus eos! Quas, optio esse ad illum quis blanditiis rerum quia.
Corrupti, ad hic velit praesentium voluptatum dolores?
</p>
<forge-toolbar slot="footer" inverted>
<forge-button id="close-dialog-button-top-sheet" slot="end" variant="raised">
Close
</forge-button>
</forge-toolbar>
</forge-scaffold>
</forge-dialog>
</div>
<script type="module">
import { IconRegistry } from "@tylertech/forge/icon";
import { tylIconClose } from "@tylertech/tyler-icons";
IconRegistry.define([tylIconClose]);
const openButton = document.getElementById("open-dialog-button-top-sheet");
const dialog = document.getElementById("dialog-top-sheet");
const closeIconButton = document.getElementById("close-dialog-icon-button-top-sheet");
const closeButton = document.getElementById("close-dialog-button-top-sheet");
openButton?.addEventListener("click", () => dialog.open = true);
closeIconButton?.addEventListener("click", () => dialog.open = false);
closeButton?.addEventListener("click", () => dialog.open = false);
dialog?.addEventListener("forge-dialog-close", () => dialog.open = false);
</script>Left sheet
A dialog sliding in from the left edge of the screen.
<div>
<forge-button id="open-dialog-button-left-sheet" variant="raised">
Open dialog
</forge-button>
<forge-dialog id="dialog-left-sheet" label="Title text" description="Lorem ipsum dolor sit amet consectetur adipisicing elit." preset="left-sheet">
<forge-scaffold class="h-auto [--forge-scaffold-width:500px]">
<forge-toolbar slot="header">
<h1 class="forge-typography--heading4 m-0" slot="start">
Title text
</h1>
<forge-icon-button id="close-dialog-icon-button-left-sheet" slot="end" aria-label="Close dialog">
<forge-icon name="close">
</forge-icon>
</forge-icon-button>
</forge-toolbar>
<p slot="body" class="p-medium">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Fugit sed pariatur error repellendus eos! Quas, optio esse ad illum quis blanditiis rerum quia.
Corrupti, ad hic velit praesentium voluptatum dolores?
</p>
<forge-toolbar slot="footer" inverted>
<forge-button id="close-dialog-button-left-sheet" slot="end" variant="raised">
Close
</forge-button>
</forge-toolbar>
</forge-scaffold>
</forge-dialog>
</div>
<script type="module">
import { IconRegistry } from "@tylertech/forge/icon";
import { tylIconClose } from "@tylertech/tyler-icons";
IconRegistry.define([tylIconClose]);
const openButton = document.getElementById("open-dialog-button-left-sheet");
const dialog = document.getElementById("dialog-left-sheet");
const closeIconButton = document.getElementById("close-dialog-icon-button-left-sheet");
const closeButton = document.getElementById("close-dialog-button-left-sheet");
openButton?.addEventListener("click", () => dialog.open = true);
closeIconButton?.addEventListener("click", () => dialog.open = false);
closeButton?.addEventListener("click", () => dialog.open = false);
dialog?.addEventListener("forge-dialog-close", () => dialog.open = false);
</script>Right sheet
A dialog sliding in from the right edge of the screen.
<div>
<forge-button id="open-dialog-button-right-sheet" variant="raised">
Open dialog
</forge-button>
<forge-dialog id="dialog-right-sheet" label="Title text" description="Lorem ipsum dolor sit amet consectetur adipisicing elit." preset="right-sheet">
<forge-scaffold class="h-auto [--forge-scaffold-width:500px]">
<forge-toolbar slot="header">
<h1 class="forge-typography--heading4 m-0" slot="start">
Title text
</h1>
<forge-icon-button id="close-dialog-icon-button-right-sheet" slot="end" aria-label="Close dialog">
<forge-icon name="close">
</forge-icon>
</forge-icon-button>
</forge-toolbar>
<p slot="body" class="p-medium">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Fugit sed pariatur error repellendus eos! Quas, optio esse ad illum quis blanditiis rerum quia.
Corrupti, ad hic velit praesentium voluptatum dolores?
</p>
<forge-toolbar slot="footer" inverted>
<forge-button id="close-dialog-button-right-sheet" slot="end" variant="raised">
Close
</forge-button>
</forge-toolbar>
</forge-scaffold>
</forge-dialog>
</div>
<script type="module">
import { IconRegistry } from "@tylertech/forge/icon";
import { tylIconClose } from "@tylertech/tyler-icons";
IconRegistry.define([tylIconClose]);
const openButton = document.getElementById("open-dialog-button-right-sheet");
const dialog = document.getElementById("dialog-right-sheet");
const closeIconButton = document.getElementById("close-dialog-icon-button-right-sheet");
const closeButton = document.getElementById("close-dialog-button-right-sheet");
openButton?.addEventListener("click", () => dialog.open = true);
closeIconButton?.addEventListener("click", () => dialog.open = false);
closeButton?.addEventListener("click", () => dialog.open = false);
dialog?.addEventListener("forge-dialog-close", () => dialog.open = false);
</script>Fullscreen
Set fullscreen (or let fullscreenThreshold trigger it automatically below a given viewport width) to expand the dialog to fill the screen — use it for a multi-step task, like a form paired with a stepper, and reserve it for mobile.
A fullscreen dialog fills the entire viewport.
<div>
<forge-button id="open-dialog-button-fullscreen" variant="raised">
Open dialog
</forge-button>
<forge-dialog id="dialog-fullscreen" label="Title text" description="Lorem ipsum dolor sit amet consectetur adipisicing elit." fullscreen>
<forge-scaffold class="h-auto">
<forge-toolbar slot="header">
<h1 class="forge-typography--heading4 m-0" slot="start">
Title text
</h1>
<forge-icon-button id="close-dialog-icon-button-fullscreen" slot="end" aria-label="Close dialog">
<forge-icon name="close">
</forge-icon>
</forge-icon-button>
</forge-toolbar>
<p slot="body" class="p-medium">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Fugit sed pariatur error repellendus eos! Quas, optio esse ad illum quis blanditiis rerum quia.
Corrupti, ad hic velit praesentium voluptatum dolores?
</p>
<forge-toolbar slot="footer" inverted>
<forge-button id="close-dialog-button-fullscreen" slot="end" variant="raised">
Close
</forge-button>
</forge-toolbar>
</forge-scaffold>
</forge-dialog>
</div>
<script type="module">
import { IconRegistry } from "@tylertech/forge/icon";
import { tylIconClose } from "@tylertech/tyler-icons";
IconRegistry.define([tylIconClose]);
const openButton = document.getElementById("open-dialog-button-fullscreen");
const dialog = document.getElementById("dialog-fullscreen");
const closeIconButton = document.getElementById("close-dialog-icon-button-fullscreen");
const closeButton = document.getElementById("close-dialog-button-fullscreen");
openButton?.addEventListener("click", () => dialog.open = true);
closeIconButton?.addEventListener("click", () => dialog.open = false);
closeButton?.addEventListener("click", () => dialog.open = false);
dialog?.addEventListener("forge-dialog-close", () => dialog.open = false);
</script>Dialogs are temporary UI elements that are used to display information, ask for input, or confirm actions. Dialogs can be modal or non-modal.
import "@tylertech/forge/dialog";| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
open | open | unknown | false | Indicates whether the dialog is open. |
mode | mode | DialogMode | 'modal' | The mode of the dialog. |
type | type | DialogType | 'dialog' | The type of the dialog. |
animationType | animation-type | DialogAnimationType | 'zoom' | The animation type of the dialog. |
preset | preset | DialogPreset | 'dialog' | The preset design that the dialog will apply. |
persistent | persistent | unknown | false | Indicates whether the dialog is dismissible via escape and backdrop click or not. |
fullscreen | fullscreen | unknown | false | Indicates whether the dialog is fullscreen or not. |
fullscreenThreshold | fullscreen-threshold | unknown | 599 | The screen width at which the dialog will switch to fullscreen. |
trigger | trigger | unknown | '' | The selector of the element that triggers the dialog. |
triggerElement | — | HTMLElement | null | null | The element that triggers the dialog. |
moveable | moveable | unknown | false | Indicates whether the dialog is moveable or not. |
positionStrategy | position-strategy | DialogPositionStrategy | 'viewport' | Controls whether the dialog is rendered relative to the viewport or its nearest containing block. |
sizeStrategy | size-strategy | DialogSizeStrategy | 'content' | Controls the block and/or inline size of the dialog. Defaults to the size of the content it contains. |
placement | placement | DialogPlacement | 'center' | The placement of the dialog. |
label | label | unknown | '' | The accessible label of the dialog. |
description | description | unknown | '' | The accessible description of the dialog. |
labelledBy | — | string | string[] | null | null | The id or ids of elements in the dialog's root node that label the dialog. Takes precedence over `label`. Use this to associate elements by id across the shadow boundary. |
describedBy | — | string | string[] | null | null | The id or ids of elements in the dialog's root node that describe the dialog. Takes precedence over `description`. Use this to associate elements by id across the shadow boundary. |
labelledByElements | — | Element[] | null | null | The elements that label the dialog. Takes precedence over `labelledBy` and `label`. |
describedByElements | — | Element[] | null | null | The elements that describe the dialog. Takes precedence over `describedBy` and `description`. |
| — | labelledby | string | string[] | null | null | The id or ids of elements in the dialog's root node that label the dialog. Takes precedence over `label`. Use this to associate elements by id across the shadow boundary. |
| — | describedby | string | string[] | null | null | The id or ids of elements in the dialog's root node that describe the dialog. Takes precedence over `description`. Use this to associate elements by id across the shadow boundary. |
| Name | Type | Description |
|---|---|---|
| CustomEvent | |
forge-dialog-open | CustomEvent<void> | Dispatched when the dialog is opened. |
forge-dialog-close | CustomEvent<void> | Dispatched when the dialog is closed. |
forge-dialog-before-close | CustomEvent<IDialogBeforeCloseEventData> | Dispatched before the dialog is closed. This event is cancelable. |
forge-dialog-move-start | CustomEvent<IDialogMoveStartEventData> | Dispatched when the dialog is first moved. |
forge-dialog-move | CustomEvent<IDialogMoveEventData> | Dispatched when the dialog is being moved. |
forge-dialog-move-end | CustomEvent<void> | Dispatched when the dialog is done being moved. |
forge-dialog-fullscreen-change | CustomEvent<boolean> | Dispatched when the dialog's fullscreen state changes. |
| Name | Description |
|---|---|
(default) | The content of the dialog. |
move-handle | The move handle content. |
| Name | Description |
|---|---|
::part(root) | The dialog container element. |
::part(backdrop) | The backdrop element. |
::part(surface) | The dialog surface element. |
::part(move-handle-container) | The alignment container for the move handle. |
::part(move-handle) | The move handle element. |
::part(move-handle-icon) | The move handle icon element. |
| Name | Description |
|---|---|
--forge-dialog-background | The background color of the dialog. |
--forge-dialog-shape | The shape of the dialog. |
--forge-dialog-elevation | The elevation of the dialog. |
--forge-dialog-spacing | The spacing between elements inside the dialog. |
--forge-dialog-block-start-spacing | The spacing at the start of the dialog block. |
--forge-dialog-block-end-spacing | The spacing at the end of the dialog block. |
--forge-dialog-inline-start-spacing | The spacing at the start of the dialog inline. |
--forge-dialog-inline-end-spacing | The spacing at the end of the dialog inline. |
--forge-dialog-padding | The padding of the dialog. |
--forge-dialog-width | The width of the dialog. |
--forge-dialog-height | The height of the dialog. |
--forge-dialog-min-width | The minimum width of the dialog. |
--forge-dialog-max-width | The maximum width of the dialog. |
--forge-dialog-min-height | The minimum height of the dialog. |
--forge-dialog-max-height | The maximum height of the dialog. |
--forge-dialog-z-index | The z-index of the dialog. |
--forge-dialog-move-handle-color | The color of the move handle. |
--forge-dialog-move-handle-size | The size of the move handle. |
--forge-dialog-move-handle-hover-cursor | The cursor style when hovering over the move handle. |
--forge-dialog-move-handle-active-cursor | The cursor style when the move handle is active. |
--forge-dialog-move-handle-spacing | The spacing around the move handle. |
--forge-dialog-move-transition-duration | The duration of the move transition. |
--forge-dialog-move-transition-easing | The easing function of the move transition. |
--forge-dialog-moving-opacity | The opacity of the dialog when it is being moved. |
--forge-dialog-enter-animation-duration | The duration of the enter animation. |
--forge-dialog-enter-animation-easing | The easing function of the enter animation. |
--forge-dialog-exit-animation-duration | The duration of the exit animation. |
--forge-dialog-exit-animation-easing | The easing function of the exit animation. |
--forge-dialog-zoom-opacity | The opacity of the dialog during zoom animation. |
--forge-dialog-zoom-scale | The scale of the dialog during zoom animation. |
--forge-dialog-fade-opacity | The opacity of the dialog during fade animation. |
--forge-dialog-slide-opacity | The opacity of the dialog during slide animation. |
--forge-dialog-slide-translate | The translation distance of the dialog during slide animation. |
--forge-dialog-backdrop-opacity | The opacity of the dialog backdrop. |
--forge-dialog-nonmodal-elevation | The elevation of non-modal dialogs. |
--forge-dialog-fullscreen-enter-animation-duration | The duration of the enter animation for fullscreen dialogs. |
--forge-dialog-fullscreen-exit-animation-duration | The duration of the exit animation for fullscreen dialogs. |
--forge-dialog-position-x | The x-axis position of the dialog. |
--forge-dialog-position-y | The y-axis position of the dialog. |
--forge-dialog-preset-sheet-enter-animation-duration | The duration of the enter animation for preset sheet dialogs. |
--forge-dialog-preset-sheet-exit-animation-duration | The duration of the exit animation for preset sheet dialogs. |
| Name | Returns | Description |
|---|---|---|
show() | void | Shows the dialog. |
hide() | void | Hides the dialog. |
Labeling
You can’t set aria-labelledby or aria-describedby directly on <forge-dialog> — the native <dialog> element underneath it lives across a shadow boundary those attributes can’t reach. Use labelledby and describedby instead: the id (or space-separated ids) of elements inside the dialog that label or describe it, such as the title in its header toolbar. The dialog resolves them across the shadow boundary, wires up the equivalent ARIA relationships, and announces them correctly when it opens. Note there’s no aria- prefix.
<forge-dialog labelledby="dialog-title" describedby="dialog-message">
<h2 id="dialog-title" class="forge-typography--heading3">Delete file?</h2>
<p id="dialog-message">This can't be undone.</p>
</forge-dialog>
labelledby and describedby were added in Forge 3.24.0. On earlier versions, or for a dialog without a visible title, set label and description to plain-text values instead. When both are set, labelledby and describedby take precedence.
<forge-dialog label="Delete file?" description="This can't be undone.">
<p>Are you sure you want to delete this file?</p>
</forge-dialog>
Give the close icon button in the header an accessible name with aria-label="Close dialog".
Keyboard interaction
Keyboard navigation needs to behave the way it looks — a modal dialog makes the rest of the page inert, so interactive content outside it shouldn’t be reachable while it’s open. If there’s a mouse-only way to close the dialog, make sure there’s a keyboard equivalent too. When the dialog closes, return focus to a logical element on the page rather than losing it.
Screen reader behavior
The dialog automatically applies role="dialog" and aria-modal="true" for you.