Dialog
Dialog is the headless modal. It manages the open state, traps focus inside the surface, dismisses on Escape or a scrim click, and binds the title and description for screen readers — and it styles none of it.
What it is
A compound component. Dialog.Root holds the open state; the parts inside read it through
context. Dialog.Content renders into a Portal, wraps the
surface in an Overlay scrim, and hands focus to a
FocusScope. The surface itself is yours — Dialog applies the ARIA
wiring and leaves the styling to the children.
Install
Anatomy
API
Dialog.Root
Dialog.Root
stablefunction Dialog.Root(props: DialogRootProps): JSX.Element
Controlled open state. Pass alongside `onOpenChange`.
Initial open state for the uncontrolled mode.
Called whenever the open state changes — by trigger, close, Escape, or scrim click.
The ARIA role. Use `alertdialog` for destructive confirmations — or reach for [AlertDialog](/headless/overlay/alert-dialog), which sets it for you.
The Trigger and Content parts.
Dialog.Trigger
Dialog.Trigger
stablefunction Dialog.Trigger(props: DialogTriggerProps): JSX.Element
A single element. Dialog clones it to inject the click handler, `aria-expanded`, and `aria-haspopup`.
Dialog.Content
Dialog.Content
stablefunction Dialog.Content(props: DialogContentProps): JSX.Element | null
Allow Escape to close the dialog.
Allow a click on the scrim to close the dialog.
Fallback timeout for an exit transition. Defaults to `0` — instant unmount. Set a positive value to keep the dialog rendered with `data-motif-state="exiting"` until the transition ends; pair with `exitStyle` on a child Box.
Inline style for the surface wrapper. The a11y wiring is applied regardless.
The dialog surface — Title, Description, Close, and your content.
Dialog.Title / Dialog.Description
Dialog.Title
stablefunction Dialog.Title(props: DialogTitleProps): JSX.Element
The element to render. Title defaults to `h2`, Description to `p`.
The title or description text. The element id is generated and bound automatically.
Dialog.Description takes the same props with an as default of "p". Both bind their generated
id into the surface's aria-labelledby / aria-describedby.
Dialog.Close
Dialog.Close
stablefunction Dialog.Close(props: DialogCloseProps): JSX.Element
A single element. Dialog clones it to add a click handler that closes the dialog.
useDialogState
useDialogState
stablefunction useDialogState(
initial?: { defaultOpen?: boolean },
): { open: boolean; setOpen: (next: boolean) => void; toggle: () => void; props: DialogControlledProps }Imperative control for callers driving the dialog from external state — a form library, a router.
Spread the returned props into Dialog.Root for controlled mode.
Dismissal
A dialog closes four ways: the Close part, Escape, a scrim click, or a controlled open change.
dismissOnEscape and dismissOnScrimClick switch the middle two off independently — set both to
false for a dialog that only closes through an explicit action, the pattern
AlertDialog bakes in.
Accessibility
Dialog implements the WAI-ARIA modal contract. The surface carries role="dialog" and
aria-modal="true". Dialog.Title and Dialog.Description generate ids that bind into
aria-labelledby and aria-describedby, so a screen reader announces both the moment the dialog
opens. The FocusScope inside Content moves focus in on open, keeps Tab cycling within the
surface, and restores focus to the trigger on close.
Always include a Dialog.Title. It is the dialog's accessible name — without it, a screen-reader
user is told a dialog opened but not what it is.
Examples
Uncontrolled — the simplest form:
Controlled — driven by useDialogState:
With an exit transition: