Dialog
A window over the page that asks the user to complete a short task without leaving the current view.
<Dialog>
<DialogTrigger render={<Button variant="outline" />}>
Rename asset
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Rename asset</DialogTitle>
<DialogDescription>
The new name appears in alerts, reports and the asset inventory.
</DialogDescription>
</DialogHeader>
<FieldGroup>
<Field>
<FieldLabel htmlFor="asset-name">Name</FieldLabel>
<Input id="asset-name" defaultValue="srv-db-01.prod" />
</Field>
<Field>
<FieldLabel htmlFor="asset-owner">Owner</FieldLabel>
<Input id="asset-owner" defaultValue="Infrastructure team" />
</Field>
</FieldGroup>
<DialogFooter>
<DialogClose render={<Button variant="outline" />}>
Cancel
</DialogClose>
<Button>Save changes</Button>
</DialogFooter>
</DialogContent>
</Dialog>Usage
import { Button } from "@zenoxai/ui/components/button"
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@zenoxai/ui/components/dialog"
<Dialog>
<DialogTrigger render={<Button variant="outline" />}>
Rename asset
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Rename asset</DialogTitle>
<DialogDescription>
The new name appears in alerts and reports.
</DialogDescription>
</DialogHeader>
{/* fields */}
<DialogFooter>
<DialogClose render={<Button variant="outline" />}>Cancel</DialogClose>
<Button>Save changes</Button>
</DialogFooter>
</DialogContent>
</Dialog>
Use a dialog for a short, focused task that blocks the page until it is done or dismissed:
renaming a record, creating a tenant, reviewing a list before export. The page behind it is
inert while it is open. Render the trigger as a Button through render, so it keeps the
button’s look and the trigger’s behavior.
Pick another overlay when the task does not fit that shape:
| Need | Use |
|---|---|
| Confirm an irreversible or consequential action | Alert Dialog |
| A long form, or details next to the list that opened them | Sheet |
| A panel on touch screens that the user swipes away | Drawer |
| A few controls anchored to the button that opened them, page stays usable | Popover |
DialogContent renders the portal, the backdrop and a close button in the top-right corner.
Hide the close button with showCloseButton={false} only when the footer offers another way out.
DialogFooter sits flush with the bottom edge on a muted band; pass showCloseButton to it to
add a “Close” button after its children.
Examples
Sizes
size sets the maximum width from the sm breakpoint up: sm (25rem, the default), md
(35rem), lg (50rem) and xl (68.75rem). Use sm for one or two fields, md for forms and
lists, lg and xl for tables or side-by-side content.
<>
{(["sm", "md", "lg", "xl"] as const).map((size) => (
<Dialog key={size}>
<DialogTrigger render={<Button variant="outline" />}>
Open {size}
</DialogTrigger>
<DialogContent size={size}>
<DialogHeader>
<DialogTitle>Dialog size {size}</DialogTitle>
<DialogDescription>
The size sets the maximum width from the sm breakpoint up. Below
it, every size fills the screen minus a 1rem margin.
</DialogDescription>
</DialogHeader>
<DialogFooter showCloseButton />
</DialogContent>
</Dialog>
))}
</>Scrollable content
The dialog grows with its content up to the height of the viewport less a 1rem margin on each
side, then scrolls as a whole, footer included. To keep the title and the actions in view, give
the long part a maximum height and overflow-y-auto, and keep the header and footer outside it.
<Dialog>
<DialogTrigger render={<Button variant="outline" />}>
View findings
</DialogTrigger>
<DialogContent size="md">
<DialogHeader>
<DialogTitle>Weekly vulnerability report</DialogTitle>
<DialogDescription>
24 findings across 3 tenants, sorted by severity.
</DialogDescription>
</DialogHeader>
<ul className="-mx-4 max-h-80 overflow-y-auto border-y border-border px-4 py-2">
{Array.from({ length: 24 }, (_, index) => (
<li
key={index}
className="flex justify-between gap-4 border-b border-border py-2 last:border-0"
>
<span>CVE-2024-{3094 + index}</span>
<span className="text-text-secondary">
srv-web-{String(index + 1).padStart(2, "0")}.prod
</span>
</li>
))}
</ul>
<DialogFooter showCloseButton>
<Button>Export PDF</Button>
</DialogFooter>
</DialogContent>
</Dialog>Controlled
Control open when the dialog must stay open while a request runs and close only when it
succeeds. Disable the fields and the buttons while it is pending.
function DialogControlled() {
const [open, setOpen] = React.useState(false)
const [saving, setSaving] = React.useState(false)
function save() {
setSaving(true)
// Stands in for the request that saves the tenant.
setTimeout(() => {
setSaving(false)
setOpen(false)
}, 1200)
}
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger render={<Button />}>New tenant</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>New tenant</DialogTitle>
<DialogDescription>
Tenants isolate assets, alerts and users of one customer.
</DialogDescription>
</DialogHeader>
<Field>
<FieldLabel htmlFor="tenant-name">Name</FieldLabel>
<Input id="tenant-name" placeholder="Acme Corp" disabled={saving} />
</Field>
<DialogFooter>
<DialogClose render={<Button variant="outline" />} disabled={saving}>
Cancel
</DialogClose>
<Button onClick={save} disabled={saving}>
{saving && <Spinner data-icon="inline-start" />}
{saving ? "Creating" : "Create tenant"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}Accessibility
- Focus moves into the dialog when it opens, stays inside while it is open and returns to the trigger when it closes. Escape and a click on the backdrop close it.
- Always render a
DialogTitle: it is the dialog’s accessible name.DialogDescriptionis read after it. If the design has no visible title, keep it and hide it withclassName="sr-only". - The built-in close button is an icon button labelled “Close” for screen readers.
API reference
Generated from the TypeScript source. className, style and children are accepted everywhere and omitted below.
Dialog
Dialog is a modal window over the page for a short, focused task: a small form, a choice, a preview. It traps focus and closes on Escape or an outside click. To confirm a destructive action, use AlertDialog or `confirm()` from the ConfirmDialog pattern; for a long form or a record's details beside a list, Sheet; on touch screens, Drawer.
| Prop | Type | Default |
|---|---|---|
actionsRef | RefObject<DialogRootActions | null>A ref to imperative actions. - `unmount`: Ends the closing phase of the dialog after an externally controlled closing animation finishes. Call `preventUnmountOnClose()` in `onOpenChange` first, otherwise the dialog completes closing on its own. Whether it leaves the DOM is decided by `keepMounted` on the portal. - `close`: Closes the dialog imperatively when called. | - |
defaultOpen | booleanWhether the dialog is initially open. | false |
defaultTriggerId | string | nullID of the trigger that the dialog is associated with. This is useful in conjunction with the `defaultOpen` prop to create an initially open dialog. | - |
disablePointerDismissal | booleanWhether to prevent the dialog from closing on outside presses. For non-modal dialogs, this also prevents the dialog from closing when focus moves outside of it. | false |
handle | DialogHandle<unknown>A handle to associate the dialog with a trigger. If specified, allows external triggers to control the dialog's open state. Can be created with the Dialog.createHandle() method. | - |
modal | boolean | "trap-focus"Determines if the dialog enters a modal state when open. - `true`: user interaction is limited to just the dialog: focus is trapped, document page scroll is locked, and pointer interactions on outside elements are disabled. - `false`: user interaction with the rest of the document is allowed. - `'trap-focus'`: focus is trapped inside the dialog, but document page scroll is not locked and pointer interactions outside of it remain enabled. | true |
onOpenChange | ((open: boolean, eventDetails: DialogRootChangeEventDetails) => void)Event handler called when the dialog is opened or closed. | - |
onOpenChangeComplete | ((open: boolean) => void)Event handler called after any animations complete when the dialog is opened or closed. | - |
open | booleanWhether the dialog is currently open. | - |
triggerId | string | nullID of the trigger that the dialog is associated with. This is useful in conjunction with the `open` prop to create a controlled dialog. There's no need to specify this prop when the dialog is uncontrolled (that is, when the `open` prop is not set). | - |
DialogClose
DialogClose closes the dialog when pressed; pass `render={<Button />}` to style it as a button.
| Prop | Type | Default |
|---|---|---|
nativeButton | booleanWhether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). | true |
render | ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DialogCloseState>Allows you to replace the component's HTML element with a different tag, or compose it with another component. | - |
DialogContent
DialogContent is the dialog box, centered in a portal over a dimmed backdrop, with a close button at its top-right corner. It is never taller than the viewport less a 16px margin on each side: longer content scrolls inside it, footer included.
| Prop | Type | Default |
|---|---|---|
finalFocus | boolean | RefObject<HTMLElement | null> | ((closeType: InteractionType) => boolean | void | HTMLElement | null)Determines the element to focus when the dialog is closed. | - |
initialFocus | boolean | RefObject<HTMLElement | null> | ((openType: InteractionType) => boolean | void | HTMLElement | null)Determines the element to focus when the dialog is opened. By default, focus moves to the first tabbable element inside the popup, except when the dialog is opened by touch — then the popup itself is focused to avoid opening the virtual keyboard. | - |
render | ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DialogPopupState>Allows you to replace the component's HTML element with a different tag, or compose it with another component. | - |
showCloseButton | booleanRenders the X button at the top-right corner. Its accessible name is the English "Close": to translate it, turn this off and render your own DialogClose. | true |
size | "sm" | "lg" | "md" | "xl"Maximum width from the sm breakpoint: `sm` 400px (default), `md` 560px, `lg` 800px, `xl` 1100px. Below it the dialog spans the viewport less a 16px margin on each side. | sm |
DialogDescription
| Prop | Type | Default |
|---|---|---|
render | ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DialogDescriptionState>Allows you to replace the component's HTML element with a different tag, or compose it with another component. | - |
DialogFooter
DialogFooter is the action bar on a muted band at the bottom of the dialog: buttons aligned right from the sm breakpoint, and stacked below it with the last one on top.
| Prop | Type | Default |
|---|---|---|
showCloseButton | booleanAppends an outline button that closes the dialog. Its text is the English "Close"; in other languages render a translated DialogClose instead. | false |
DialogHeader
No props of its own. Accepts the props of the element it renders.
DialogOverlay
DialogOverlay is the dimmed, blurred backdrop. DialogContent renders it already.
| Prop | Type | Default |
|---|---|---|
forceRender | booleanWhether the backdrop is forced to render even when nested. | false |
render | ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DialogBackdropState>Allows you to replace the component's HTML element with a different tag, or compose it with another component. | - |
DialogPortal
DialogPortal moves the dialog to the end of the document body. DialogContent renders it already.
| Prop | Type | Default |
|---|---|---|
container | HTMLElement | ShadowRoot | RefObject<HTMLElement | ShadowRoot | null> | nullA parent element to render the portal element into. | - |
keepMounted | booleanWhether to keep the portal mounted in the DOM while the popup is hidden. | false |
render | ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DialogPortalState>Allows you to replace the component's HTML element with a different tag, or compose it with another component. | - |
DialogTitle
| Prop | Type | Default |
|---|---|---|
render | ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DialogTitleState>Allows you to replace the component's HTML element with a different tag, or compose it with another component. | - |
DialogTrigger
| Prop | Type | Default |
|---|---|---|
handle | DialogHandle<unknown>A handle to associate the trigger with a dialog. Can be created with the Dialog.createHandle() method. | - |
id | stringID of the trigger. In addition to being forwarded to the rendered element, it is also used to specify the active trigger for the dialog in controlled mode (with the DialogRoot `triggerId` prop). | - |
nativeButton | booleanWhether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). | true |
payload | unknownA payload to pass to the dialog when it is opened. | - |
render | ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DialogTriggerState>Allows you to replace the component's HTML element with a different tag, or compose it with another component. | - |