Dialog
View as MarkdownA popup that opens on top of the entire page.
<ui:dialog.root>
<ui:dialog.trigger asChild="{true}">
<ui:button>Open Dialog</ui:button>
</ui:dialog.trigger>
<ui:dialog.content>
<ui:dialog.header>
<ui:dialog.title>Dialog Title</ui:dialog.title>
<ui:dialog.description>This is a simple dialog example.</ui:dialog.description>
</ui:dialog.header>
<ui:dialog.footer>
<ui:dialog.close asChild="{true}">
<ui:button>Close</ui:button>
</ui:dialog.close>
</ui:dialog.footer>
</ui:dialog.content>
</ui:dialog.root>
import { mountAll } from 'fluid-primitives';
import { Dialog } from 'fluid-primitives/dialog';
mountAll('dialog', ({ props }) => {
const dialog = new Dialog(props);
dialog.init();
return dialog;
});
Features
- Focus is trapped within the dialog and restored when closed
- Supports modal and non-modal modes
- Scrolling is blocked when dialog is open (in modal mode)
- Pressing
Escapecloses the dialog - Supports controlled and uncontrolled open state
Installation
typo3 ui:add dialog
Please copy the files manually from GitHub into your project.
Read more about installing Components and Primitives.
Examples
Alert Dialog
For critical confirmations or destructive actions, use role="alertdialog". Alert dialogs differ from regular dialogs in important ways:
- Automatic focus: The close/cancel button receives focus when opened, prioritizing the safest action
- Requires explicit dismissal: Cannot be closed by clicking outside, only via button clicks or Escape key
<ui:dialog.root role="alertdialog">
<ui:dialog.trigger asChild="{true}">
<ui:button>Delete Item</ui:button>
</ui:dialog.trigger>
<ui:dialog.content>
<ui:dialog.header>
<ui:dialog.title>Are you sure?</ui:dialog.title>
<ui:dialog.description>This action cannot be undone. This will permanently delete your item.</ui:dialog.description>
</ui:dialog.header>
<ui:dialog.footer>
<ui:dialog.close asChild="{true}">
<ui:button variant="secondary">Cancel</ui:button>
</ui:dialog.close>
<ui:button>Delete</ui:button>
</ui:dialog.footer>
</ui:dialog.content>
</ui:dialog.root>
Nested Dialogs
Open a dialog from within another dialog.
<ui:dialog.root rootId="parent-dialog">
<ui:dialog.trigger asChild="{true}">
<ui:button>Open first Dialog</ui:button>
</ui:dialog.trigger>
<ui:dialog.content>
<ui:dialog.header>
<ui:dialog.title>Dialog Title</ui:dialog.title>
<ui:dialog.description>This is a simple dialog example.</ui:dialog.description>
</ui:dialog.header>
<ui:dialog.footer>
<ui:dialog.close asChild="{true}">
<ui:button variant="secondary">Close</ui:button>
</ui:dialog.close>
<ui:dialog.root rootId="nested-dialog">
<ui:dialog.trigger asChild="{true}">
<ui:button>Open nested Dialog</ui:button>
</ui:dialog.trigger>
<ui:dialog.content>
<ui:dialog.header>
<ui:dialog.title>Nested Dialog Title</ui:dialog.title>
<ui:dialog.description>This is a nested dialog example.</ui:dialog.description>
</ui:dialog.header>
<ui:dialog.footer>
<ui:dialog.close asChild="{true}">
<ui:button variant="secondary">Close nested Dialog</ui:button>
</ui:dialog.close>
<ui:dialog.root rootId="nested-nested-dialog">
<ui:dialog.trigger asChild="{true}">
<ui:button>Open nested nested Dialog</ui:button>
</ui:dialog.trigger>
<ui:dialog.content>
<ui:dialog.header>
<ui:dialog.title>Nested Nested Dialog Title</ui:dialog.title>
<ui:dialog.description>This is a nested nested dialog example.</ui:dialog.description>
</ui:dialog.header>
<ui:dialog.footer>
<ui:dialog.close asChild="{true}">
<ui:button>Close nested nested Dialog</ui:button>
</ui:dialog.close>
</ui:dialog.footer>
</ui:dialog.content>
</ui:dialog.root>
</ui:dialog.footer>
</ui:dialog.content>
</ui:dialog.root>
</ui:dialog.footer>
</ui:dialog.content>
</ui:dialog.root>
Popover Inside Dialog
Render a popover inside dialog content when you need anchored secondary actions without breaking dialog focus management.
<ui:dialog.root>
<ui:dialog.trigger asChild="{true}">
<ui:button>Open Dialog</ui:button>
</ui:dialog.trigger>
<ui:dialog.content>
<ui:dialog.header>
<ui:dialog.title>Dialog with Popover</ui:dialog.title>
<ui:dialog.description>Popover stays interactive inside dialog without leaving dialog content.</ui:dialog.description>
</ui:dialog.header>
<div class="space-y-4">
<p class="text-muted-foreground text-sm">
Use this pattern when dialog needs smaller anchored surface for secondary actions or extra context.
</p>
<ui:popover.root>
<ui:popover.trigger asChild="{true}">
<ui:button>Open Popover</ui:button>
</ui:popover.trigger>
<ui:popover.content>
<ui:popover.title>Share settings</ui:popover.title>
<ui:popover.description>
Choose who can access this dialog item without closing dialog.
</ui:popover.description>
<div class="mt-3 flex gap-2">
<ui:button size="sm">Copy Link</ui:button>
<ui:popover.close asChild="{true}">
<ui:button size="sm" variant="secondary">Dismiss</ui:button>
</ui:popover.close>
</div>
</ui:popover.content>
</ui:popover.root>
</div>
<ui:dialog.footer>
<ui:dialog.close asChild="{true}">
<ui:button variant="secondary">Close</ui:button>
</ui:dialog.close>
</ui:dialog.footer>
</ui:dialog.content>
</ui:dialog.root>
Scrollable Outside
The dialog positioner can scroll when the content exceeds the viewport height, so the entire dialog can scroll together.
<ui:dialog.root>
<ui:dialog.trigger asChild="{true}">
<ui:button>Open Dialog</ui:button>
</ui:dialog.trigger>
<ui:dialog.content>
<ui:dialog.header>
<ui:dialog.title>Dialog Title</ui:dialog.title>
<ui:dialog.description>This whole dialog is scrollable when the content exceeds the viewport height.</ui:dialog.description>
</ui:dialog.header>
<div class="text-sm leading-relaxed space-y-4">
<p>
Est nulla commodo laboris velit ipsum dolore. Magna id anim Lorem labore minim aute irure laboris. Velit elit duis exercitation duis mollit enim cupidatat consectetur mollit non officia deserunt fugiat do deserunt.
</p>
<p>
Fugiat adipisicing dolor ut irure. Ut aute Lorem labore est aute quis officia quis consectetur in amet id velit dolore. Occaecat labore adipisicing nostrud veniam. Eu non tempor ipsum. Sunt irure enim enim sint magna laboris ex tempor excepteur nulla est amet culpa.
</p>
<p>
Est nulla commodo laboris velit ipsum dolore. Magna id anim Lorem labore minim aute irure laboris. Velit elit duis exercitation duis mollit enim cupidatat consectetur mollit non officia deserunt fugiat do deserunt.
</p>
<p>
Fugiat adipisicing dolor ut irure. Ut aute Lorem labore est aute quis officia quis consectetur in amet id velit dolore. Occaecat labore adipisicing nostrud veniam. Eu non tempor ipsum. Sunt irure enim enim sint magna laboris ex tempor excepteur nulla est amet culpa.
</p>
<p>
Est nulla commodo laboris velit ipsum dolore. Magna id anim Lorem labore minim aute irure laboris. Velit elit duis exercitation duis mollit enim cupidatat consectetur mollit non officia deserunt fugiat do deserunt.
</p>
<p>
Est nulla commodo laboris velit ipsum dolore. Magna id anim Lorem labore minim aute irure laboris. Velit elit duis exercitation duis mollit enim cupidatat consectetur mollit non officia deserunt fugiat do deserunt.
</p>
<p>
Fugiat adipisicing dolor ut irure. Ut aute Lorem labore est aute quis officia quis consectetur in amet id velit dolore. Occaecat labore adipisicing nostrud veniam. Eu non tempor ipsum. Sunt irure enim enim sint magna laboris ex tempor excepteur nulla est amet culpa.
</p>
<p>
Est nulla commodo laboris velit ipsum dolore. Magna id anim Lorem labore minim aute irure laboris. Velit elit duis exercitation duis mollit enim cupidatat consectetur mollit non officia deserunt fugiat do deserunt.
</p>
<p>
Est nulla commodo laboris velit ipsum dolore. Magna id anim Lorem labore minim aute irure laboris. Velit elit duis exercitation duis mollit enim cupidatat consectetur mollit non officia deserunt fugiat do deserunt.
</p>
<p>
Fugiat adipisicing dolor ut irure. Ut aute Lorem labore est aute quis officia quis consectetur in amet id velit dolore. Occaecat labore adipisicing nostrud veniam. Eu non tempor ipsum. Sunt irure enim enim sint magna laboris ex tempor excepteur nulla est amet culpa.
</p>
<p>
Est nulla commodo laboris velit ipsum dolore. Magna id anim Lorem labore minim aute irure laboris. Velit elit duis exercitation duis mollit enim cupidatat consectetur mollit non officia deserunt fugiat do deserunt.
</p>
<p>
Est nulla commodo laboris velit ipsum dolore. Magna id anim Lorem labore minim aute irure laboris. Velit elit duis exercitation duis mollit enim cupidatat consectetur mollit non officia deserunt fugiat do deserunt.
</p>
<p>
Est nulla commodo laboris velit ipsum dolore. Magna id anim Lorem labore minim aute irure laboris. Velit elit duis exercitation duis mollit enim cupidatat consectetur mollit non officia deserunt fugiat do deserunt.
</p>
<p>
Fugiat adipisicing dolor ut irure. Ut aute Lorem labore est aute quis officia quis consectetur in amet id velit dolore. Occaecat labore adipisicing nostrud veniam. Eu non tempor ipsum. Sunt irure enim enim sint magna laboris ex tempor excepteur nulla est amet culpa.
</p>
<p>
Est nulla commodo laboris velit ipsum dolore. Magna id anim Lorem labore minim aute irure laboris. Velit elit duis exercitation duis mollit enim cupidatat consectetur mollit non officia deserunt fugiat do deserunt.
</p>
</div>
<ui:dialog.footer>
<ui:dialog.close asChild="{true}">
<ui:button>Close</ui:button>
</ui:dialog.close>
</ui:dialog.footer>
</ui:dialog.content>
</ui:dialog.root>
Scrollable Inside
With some additions, the dialog content can scroll when it exceeds the viewport height, while keeping the header and footer visible.
Set a max-height on the content and use overflow-y: auto to enable scrolling inside the dialog's content area.
<ui:dialog.root>
<ui:dialog.trigger asChild="{true}">
<ui:button>Open Dialog</ui:button>
</ui:dialog.trigger>
<ui:dialog.content class="max-h-[90dvh]">
<ui:dialog.header>
<ui:dialog.title>Dialog Title</ui:dialog.title>
<ui:dialog.description>The content of this dialog is scrollable while the header and footer remain fixed.</ui:dialog.description>
</ui:dialog.header>
<div class="text-sm leading-relaxed space-y-4 overflow-y-auto">
<p>
Est nulla commodo laboris velit ipsum dolore. Magna id anim Lorem labore minim aute irure laboris. Velit elit duis exercitation duis mollit enim cupidatat consectetur mollit non officia deserunt fugiat do deserunt.
</p>
<p>
Fugiat adipisicing dolor ut irure. Ut aute Lorem labore est aute quis officia quis consectetur in amet id velit dolore. Occaecat labore adipisicing nostrud veniam. Eu non tempor ipsum. Sunt irure enim enim sint magna laboris ex tempor excepteur nulla est amet culpa.
</p>
<p>
Est nulla commodo laboris velit ipsum dolore. Magna id anim Lorem labore minim aute irure laboris. Velit elit duis exercitation duis mollit enim cupidatat consectetur mollit non officia deserunt fugiat do deserunt.
</p>
<p>
Fugiat adipisicing dolor ut irure. Ut aute Lorem labore est aute quis officia quis consectetur in amet id velit dolore. Occaecat labore adipisicing nostrud veniam. Eu non tempor ipsum. Sunt irure enim enim sint magna laboris ex tempor excepteur nulla est amet culpa.
</p>
<p>
Est nulla commodo laboris velit ipsum dolore. Magna id anim Lorem labore minim aute irure laboris. Velit elit duis exercitation duis mollit enim cupidatat consectetur mollit non officia deserunt fugiat do deserunt.
</p>
<p>
Est nulla commodo laboris velit ipsum dolore. Magna id anim Lorem labore minim aute irure laboris. Velit elit duis exercitation duis mollit enim cupidatat consectetur mollit non officia deserunt fugiat do deserunt.
</p>
<p>
Fugiat adipisicing dolor ut irure. Ut aute Lorem labore est aute quis officia quis consectetur in amet id velit dolore. Occaecat labore adipisicing nostrud veniam. Eu non tempor ipsum. Sunt irure enim enim sint magna laboris ex tempor excepteur nulla est amet culpa.
</p>
<p>
Est nulla commodo laboris velit ipsum dolore. Magna id anim Lorem labore minim aute irure laboris. Velit elit duis exercitation duis mollit enim cupidatat consectetur mollit non officia deserunt fugiat do deserunt.
</p>
<p>
Est nulla commodo laboris velit ipsum dolore. Magna id anim Lorem labore minim aute irure laboris. Velit elit duis exercitation duis mollit enim cupidatat consectetur mollit non officia deserunt fugiat do deserunt.
</p>
<p>
Fugiat adipisicing dolor ut irure. Ut aute Lorem labore est aute quis officia quis consectetur in amet id velit dolore. Occaecat labore adipisicing nostrud veniam. Eu non tempor ipsum. Sunt irure enim enim sint magna laboris ex tempor excepteur nulla est amet culpa.
</p>
<p>
Est nulla commodo laboris velit ipsum dolore. Magna id anim Lorem labore minim aute irure laboris. Velit elit duis exercitation duis mollit enim cupidatat consectetur mollit non officia deserunt fugiat do deserunt.
</p>
<p>
Est nulla commodo laboris velit ipsum dolore. Magna id anim Lorem labore minim aute irure laboris. Velit elit duis exercitation duis mollit enim cupidatat consectetur mollit non officia deserunt fugiat do deserunt.
</p>
<p>
Est nulla commodo laboris velit ipsum dolore. Magna id anim Lorem labore minim aute irure laboris. Velit elit duis exercitation duis mollit enim cupidatat consectetur mollit non officia deserunt fugiat do deserunt.
</p>
<p>
Fugiat adipisicing dolor ut irure. Ut aute Lorem labore est aute quis officia quis consectetur in amet id velit dolore. Occaecat labore adipisicing nostrud veniam. Eu non tempor ipsum. Sunt irure enim enim sint magna laboris ex tempor excepteur nulla est amet culpa.
</p>
<p>
Est nulla commodo laboris velit ipsum dolore. Magna id anim Lorem labore minim aute irure laboris. Velit elit duis exercitation duis mollit enim cupidatat consectetur mollit non officia deserunt fugiat do deserunt.
</p>
</div>
<ui:dialog.footer>
<ui:dialog.close asChild="{true}">
<ui:button>Close</ui:button>
</ui:dialog.close>
</ui:dialog.footer>
</ui:dialog.content>
</ui:dialog.root>
Prevent Close on Outside Click
Keep the dialog open when clicking outside.
<ui:dialog.root closeOnInteractOutside="{false}">
<ui:dialog.trigger asChild="{true}">
<ui:button>Open Dialog</ui:button>
</ui:dialog.trigger>
<ui:dialog.content>
<ui:dialog.header>
<ui:dialog.title>Persistent Dialog</ui:dialog.title>
<ui:dialog.description>Click outside won't close this dialog.</ui:dialog.description>
</ui:dialog.header>
<ui:dialog.footer>
<ui:dialog.close asChild="{true}">
<ui:button>Close</ui:button>
</ui:dialog.close>
</ui:dialog.footer>
</ui:dialog.content>
</ui:dialog.root>
Prevent Close on Escape
Disable closing the dialog with the Escape key.
<ui:dialog.root closeOnEscape="{false}">
<ui:dialog.trigger asChild="{true}">
<ui:button>Open Dialog</ui:button>
</ui:dialog.trigger>
<ui:dialog.content>
<ui:dialog.header>
<ui:dialog.title>No Escape</ui:dialog.title>
<ui:dialog.description>Pressing Escape won't close this dialog.</ui:dialog.description>
</ui:dialog.header>
<ui:dialog.footer>
<ui:dialog.close asChild="{true}">
<ui:button>Close</ui:button>
</ui:dialog.close>
</ui:dialog.footer>
</ui:dialog.content>
</ui:dialog.root>
API Reference
The following tables cover the available props of the Fluid Primitives. For a full list of available client side props and methods, see the Zag.js Machine API.
dialog.root
Provides dialog state and context for the composed parts. Renders no wrapper element.
| Name | Description | Required | Default |
|---|---|---|---|
trapFocus | booleanWhether to trap focus inside the dialog when it's opened. | No | true |
preventScroll | booleanWhether to prevent scrolling behind the dialog when it's opened. | No | true |
modal | booleanWhether to prevent pointer interaction outside the element and hide all content below it. | No | true |
restoreFocus | booleanWhether to restore focus to the element that had focus before the dialog was opened. | No | true |
closeOnInteractOutside | booleanWhether to close the dialog when the outside is clicked. | No | true |
closeOnEscape | booleanWhether to close the dialog when the escape key is pressed. | No | true |
role | stringThe dialog's role. | No | 'dialog' |
defaultOpen | booleanThe initial open state of the dialog when rendered. Use when you don't need to control the open state of the dialog. | No | false |
rootId | stringThe root ID of the component, used for hydration and identification. | No | - |
ids | arrayThe IDs of of the component parts for composition. | No | [] |
controlled | booleanIf true, the component is meant to be initialized manually inside another component | No | false |
dialog.trigger
Opens the dialog. Renders a <button> element.
| Name | Description | Required | Default |
|---|---|---|---|
value | string | No | - |
asChild | booleanIf true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. | No | - |
class | stringThe CSS class(es) to be applied to the component. | No | - |
attributes | arrayAdditional attributes that should be rendered on the component where ui:attributes is used. | No | [] |
Rendered data attributes
| Attribute | Description |
|---|---|
data-scope | dialog |
data-part | trigger |
data-value | The value of the item |
data-state | "open" | "closed" |
data-current | Present when current |
dialog.backdrop
Displays the overlay behind the dialog content. Renders a <div> element.
| Name | Description | Required | Default |
|---|---|---|---|
asChild | booleanIf true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. | No | - |
class | stringThe CSS class(es) to be applied to the component. | No | - |
attributes | arrayAdditional attributes that should be rendered on the component where ui:attributes is used. | No | [] |
Rendered data attributes
| Attribute | Description |
|---|---|
data-scope | dialog |
data-part | backdrop |
data-state | "open" | "closed" |
dialog.positioner
Positions the dialog content within the viewport. Renders a <div> element.
| Name | Description | Required | Default |
|---|---|---|---|
asChild | booleanIf true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. | No | - |
class | stringThe CSS class(es) to be applied to the component. | No | - |
attributes | arrayAdditional attributes that should be rendered on the component where ui:attributes is used. | No | [] |
dialog.content
Contains the dialog surface and interactive content. Renders a <div> element.
| Name | Description | Required | Default |
|---|---|---|---|
asChild | booleanIf true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. | No | - |
class | stringThe CSS class(es) to be applied to the component. | No | - |
attributes | arrayAdditional attributes that should be rendered on the component where ui:attributes is used. | No | [] |
Rendered data attributes
| Attribute | Description |
|---|---|
data-scope | dialog |
data-part | content |
data-state | "open" | "closed" |
data-nested | dialog |
data-has-nested | dialog |
dialog.title
Provides the accessible title for the dialog. Renders a <div> element.
| Name | Description | Required | Default |
|---|---|---|---|
asChild | booleanIf true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. | No | - |
class | stringThe CSS class(es) to be applied to the component. | No | - |
attributes | arrayAdditional attributes that should be rendered on the component where ui:attributes is used. | No | [] |
dialog.description
Provides supporting descriptive text for the dialog. Renders a <div> element.
| Name | Description | Required | Default |
|---|---|---|---|
asChild | booleanIf true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. | No | - |
class | stringThe CSS class(es) to be applied to the component. | No | - |
attributes | arrayAdditional attributes that should be rendered on the component where ui:attributes is used. | No | [] |
dialog.closeTrigger
Closes the dialog when activated. Renders a <button> element.
| Name | Description | Required | Default |
|---|---|---|---|
asChild | booleanIf true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. | No | - |
class | stringThe CSS class(es) to be applied to the component. | No | - |
attributes | arrayAdditional attributes that should be rendered on the component where ui:attributes is used. | No | [] |
Machine JavaScript API
| Name | Type | Description |
|---|---|---|
open | boolean | Whether the dialog is open |
setOpen | (open: boolean) => void | Function to open or close the dialog |
triggerValue | string | null | The active trigger value |
setTriggerValue | (value: string | null) => void | Function to set the active trigger value |
Accessibility
| Key | Description |
|---|---|
Enter | When focus is on the trigger, opens the dialog. |
Tab | Moves focus to the next focusable element within the content. Focus is trapped within the dialog. |
Shift + Tab | Moves focus to the previous focusable element. Focus is trapped within the dialog. |
Esc | Closes the dialog and moves focus to trigger or the defined final focus element |
Anatomy
<primitives:dialog.root>
<primitives:dialog.trigger />
<primitives:dialog.backdrop />
<primitives:dialog.positioner>
<primitives:dialog.content>
<primitives:dialog.title />
<primitives:dialog.description />
</primitives:dialog.content>
</primitives:dialog.positioner>
</primitives:dialog.root>