{"slug":"dialog","title":"Dialog","description":"Using the dialog machine in your project.","contentType":"component","framework":"react","content":"A dialog is a window overlaid on either the primary window or another dialog\nwindow. Content behind a modal dialog is inert, meaning that users cannot\ninteract with it.\n\n## Resources\n\n\n[Latest version: v1.35.3](https://www.npmjs.com/package/@zag-js/dialog)\n[Logic Visualizer](https://zag-visualizer.vercel.app/dialog)\n[Source Code](https://github.com/chakra-ui/zag/tree/main/packages/machines/dialog)\n\n\n\n**Features**\n\n- Supports modal and non-modal modes\n- Focus is trapped and scrolling is blocked in the modal mode\n- Provides screen reader announcements via rendered title and description\n- Pressing `Esc` closes the dialog\n\n## Installation\n\nInstall the dialog package:\n\n```bash\nnpm install @zag-js/dialog @zag-js/react\n# or\nyarn add @zag-js/dialog @zag-js/react\n```\n\n## Anatomy\n\nTo use the dialog component correctly, you'll need to understand its anatomy and\nhow we name its parts.\n\n> Each part includes a `data-part` attribute to help identify them in the DOM.\n\n\n\n## Usage\n\nImport the dialog package:\n\n```jsx\nimport * as dialog from \"@zag-js/dialog\"\n```\n\nThe dialog package exports two key functions:\n\n- `machine` - Behavior logic for the dialog.\n- `connect` - Maps behavior to JSX props and event handlers.\n\n> Pass a unique `id` to `useMachine` so generated element ids stay predictable.\n\nThen use the framework integration helpers:\n\n```tsx\nimport * as dialog from \"@zag-js/dialog\"\nimport { useMachine, normalizeProps, Portal } from \"@zag-js/react\"\n\nexport function Dialog() {\n  const service = useMachine(dialog.machine, { id: \"1\" })\n\n  const api = dialog.connect(service, normalizeProps)\n\n  return (\n    <>\n      <button {...api.getTriggerProps()}>Open Dialog</button>\n      {api.open && (\n        <Portal>\n          <div {...api.getBackdropProps()} />\n          <div {...api.getPositionerProps()}>\n            <div {...api.getContentProps()}>\n              <h2 {...api.getTitleProps()}>Edit profile</h2>\n              <p {...api.getDescriptionProps()}>\n                Make changes to your profile here. Click save when you are done.\n              </p>\n              <div>\n                <input placeholder=\"Enter name...\" />\n                <button>Save</button>\n              </div>\n              <button {...api.getCloseTriggerProps()}>Close</button>\n            </div>\n          </div>\n        </Portal>\n      )}\n    </>\n  )\n}\n```\n\n### Managing focus within the dialog\n\nWhen the dialog opens, it focuses the first focusable element and keeps keyboard\nfocus inside the dialog.\n\nTo control what receives focus on open, pass `initialFocusEl`.\n\n```jsx {3,6,13}\nexport function Dialog() {\n  // initial focused element ref\n  const inputRef = useRef(null)\n\n  const service = useMachine(dialog.machine, {\n    initialFocusEl: () => inputRef.current,\n  })\n\n  // ...\n\n  return (\n    //...\n    <input ref={inputRef} />\n    // ...\n  )\n}\n```\n\nTo control what receives focus when the dialog closes, pass `finalFocusEl`.\n\n### Dialog vs non-modal dialog\n\nSet `modal` to `false` to allow interaction with content behind the dialog.\n\n```jsx\nconst service = useMachine(dialog.machine, {\n  modal: false,\n})\n```\n\n### Closing the dialog on interaction outside\n\nBy default, the dialog closes when you click its overlay. You can set\n`closeOnInteractOutside` to `false` if you want the modal to stay visible.\n\n```jsx {2}\nconst service = useMachine(dialog.machine, {\n  closeOnInteractOutside: false,\n})\n```\n\nYou can also customize the behavior by passing a function to the\n`onInteractOutside` callback and calling `event.preventDefault()`.\n\n```jsx {2-7}\nconst service = useMachine(dialog.machine, {\n  onInteractOutside(event) {\n    const target = event.target\n    if (target?.closest(\"<selector>\")) {\n      return event.preventDefault()\n    }\n  },\n})\n```\n\n### Listening for open state changes\n\nWhen the dialog is opened or closed, the `onOpenChange` callback is invoked.\n\n```jsx {2-7}\nconst service = useMachine(dialog.machine, {\n  onOpenChange(details) {\n    // details => { open: boolean }\n    console.log(\"open:\", details.open)\n  },\n})\n```\n\n### Closing with Escape\n\nSet `closeOnEscape` to `false` if the dialog should not close on `Esc`.\n\n```jsx\nconst service = useMachine(dialog.machine, {\n  closeOnEscape: false,\n})\n```\n\n### Controlled dialog\n\nTo control the dialog's open state, pass the `open` and `onOpenChange`\nproperties.\n\n```tsx\nimport { useState } from \"react\"\n\nexport function ControlledDialog() {\n  const [open, setOpen] = useState(false)\n\n  const service = useMachine(dialog.machine, {\n    open,\n    onOpenChange(details) {\n      setOpen(details.open)\n    },\n  })\n\n  return (\n    // ...\n  )\n}\n```\n\n### Controlling the scroll behavior\n\nWhen the dialog is open, it prevents scrolling on the `body` element. To disable\nthis behavior, set `preventScroll` to `false`.\n\n```jsx {2}\nconst service = useMachine(dialog.machine, {\n  preventScroll: false,\n})\n```\n\n### Creating an alert dialog\n\nThe dialog supports both `dialog` and `alertdialog` roles. It uses `dialog` by\ndefault. Set `role` to `alertdialog` for urgent actions.\n\nThat's it! Now you have an alert dialog.\n\n```jsx {2}\nconst service = useMachine(dialog.machine, {\n  role: \"alertdialog\",\n})\n```\n\n> By definition, an alert dialog will contain two or more action buttons. We\n> recommend setting focus to the least destructive action via `initialFocusEl`.\n\n### Labeling without a visible title\n\nIf you do not render a title element, provide `aria-label`.\n\n```jsx\nconst service = useMachine(dialog.machine, {\n  \"aria-label\": \"Delete project\",\n})\n```\n\n## Styling guide\n\nEach part includes a `data-part` attribute you can target in CSS.\n\n```css\n[data-part=\"trigger\"] {\n  /* styles for the trigger element */\n}\n\n[data-part=\"backdrop\"] {\n  /* styles for the backdrop element */\n}\n\n[data-part=\"positioner\"] {\n  /* styles for the positioner element */\n}\n\n[data-part=\"content\"] {\n  /* styles for the content element */\n}\n\n[data-part=\"title\"] {\n  /* styles for the title element */\n}\n\n[data-part=\"description\"] {\n  /* styles for the description element */\n}\n\n[data-part=\"close-trigger\"] {\n  /* styles for the close trigger element */\n}\n```\n\n### Open and closed state\n\nThe dialog has two states: `open` and `closed`. You can use the `data-state`\nattribute to style the dialog or trigger based on its state.\n\n```css\n[data-part=\"content\"][data-state=\"open|closed\"] {\n  /* styles for the open state */\n}\n\n[data-part=\"trigger\"][data-state=\"open|closed\"] {\n  /* styles for the open state */\n}\n```\n\n### Nested dialogs\n\nWhen dialogs are nested (a dialog opened from within another dialog), the layer\nstack automatically applies data attributes to help create visual hierarchy.\n\n- `data-nested` - Applied to nested dialogs\n- `data-has-nested` - Applied to dialogs that have nested dialogs open\n- `--nested-layer-count` - CSS variable indicating the number of nested dialogs\n\n```css\n/* Scale down parent dialogs when they have nested children */\n[data-part=\"content\"][data-has-nested] {\n  transform: scale(calc(1 - var(--nested-layer-count) * 0.05));\n  transition: transform 0.2s ease-in-out;\n}\n\n/* Style nested dialogs differently */\n[data-part=\"content\"][data-nested] {\n  border: 2px solid var(--accent-color);\n}\n\n/* Create depth effect using backdrop opacity */\n[data-part=\"backdrop\"][data-has-nested] {\n  opacity: calc(0.4 + var(--nested-layer-count) * 0.1);\n}\n```\n\n## Methods and Properties\n\n### Machine Context\n\nThe dialog machine exposes the following context properties:\n\n**`ids`**\nType: `Partial<{ trigger: string; positioner: string; backdrop: string; content: string; closeTrigger: string; title: string; description: string; }>`\nDescription: The ids of the elements in the dialog. Useful for composition.\n\n**`trapFocus`**\nType: `boolean`\nDescription: Whether to trap focus inside the dialog when it's opened\n\n**`preventScroll`**\nType: `boolean`\nDescription: Whether to prevent scrolling behind the dialog when it's opened\n\n**`modal`**\nType: `boolean`\nDescription: Whether to prevent pointer interaction outside the element and hide all content below it\n\n**`initialFocusEl`**\nType: `() => HTMLElement`\nDescription: Element to receive focus when the dialog is opened\n\n**`finalFocusEl`**\nType: `() => HTMLElement`\nDescription: Element to receive focus when the dialog is closed\n\n**`restoreFocus`**\nType: `boolean`\nDescription: Whether to restore focus to the element that had focus before the dialog was opened\n\n**`closeOnInteractOutside`**\nType: `boolean`\nDescription: Whether to close the dialog when the outside is clicked\n\n**`closeOnEscape`**\nType: `boolean`\nDescription: Whether to close the dialog when the escape key is pressed\n\n**`aria-label`**\nType: `string`\nDescription: Human readable label for the dialog, in event the dialog title is not rendered\n\n**`role`**\nType: `\"dialog\" | \"alertdialog\"`\nDescription: The dialog's role\n\n**`open`**\nType: `boolean`\nDescription: The controlled open state of the dialog\n\n**`defaultOpen`**\nType: `boolean`\nDescription: The initial open state of the dialog when rendered.\nUse when you don't need to control the open state of the dialog.\n\n**`onOpenChange`**\nType: `(details: OpenChangeDetails) => void`\nDescription: Function to call when the dialog's open state changes\n\n**`dir`**\nType: `\"ltr\" | \"rtl\"`\nDescription: The document's text/writing direction.\n\n**`id`**\nType: `string`\nDescription: The unique identifier of the machine.\n\n**`getRootNode`**\nType: `() => Node | ShadowRoot | Document`\nDescription: A root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.\n\n**`onEscapeKeyDown`**\nType: `(event: KeyboardEvent) => void`\nDescription: Function called when the escape key is pressed\n\n**`onRequestDismiss`**\nType: `(event: LayerDismissEvent) => void`\nDescription: Function called when this layer is closed due to a parent layer being closed\n\n**`onPointerDownOutside`**\nType: `(event: PointerDownOutsideEvent) => void`\nDescription: Function called when the pointer is pressed down outside the component\n\n**`onFocusOutside`**\nType: `(event: FocusOutsideEvent) => void`\nDescription: Function called when the focus is moved outside the component\n\n**`onInteractOutside`**\nType: `(event: InteractOutsideEvent) => void`\nDescription: Function called when an interaction happens outside the component\n\n**`persistentElements`**\nType: `(() => Element)[]`\nDescription: Returns the persistent elements that:\n- should not have pointer-events disabled\n- should not trigger the dismiss event\n\n### Machine API\n\nThe dialog `api` exposes the following methods:\n\n**`open`**\nType: `boolean`\nDescription: Whether the dialog is open\n\n**`setOpen`**\nType: `(open: boolean) => void`\nDescription: Function to open or close the dialog\n\n### Data Attributes\n\n**`Trigger`**\n\n**`data-scope`**: dialog\n**`data-part`**: trigger\n**`data-state`**: \"open\" | \"closed\"\n\n**`Backdrop`**\n\n**`data-scope`**: dialog\n**`data-part`**: backdrop\n**`data-state`**: \"open\" | \"closed\"\n\n**`Content`**\n\n**`data-scope`**: dialog\n**`data-part`**: content\n**`data-state`**: \"open\" | \"closed\"\n**`data-nested`**: dialog\n**`data-has-nested`**: dialog\n\n### CSS Variables\n\n<CssVarTable name=\"dialog\" />\n\n## Accessibility\n\nAdheres to the\n[Alert and Message Dialogs WAI-ARIA design pattern](https://www.w3.org/WAI/ARIA/apg/patterns/alertdialog).\n\n### Keyboard Interactions\n\n**`Enter`**\nDescription: When focus is on the trigger, opens the dialog.\n\n**`Tab`**\nDescription: Moves focus to the next focusable element within the content. Focus is trapped within the dialog.\n\n**`Shift + Tab`**\nDescription: Moves focus to the previous focusable element. Focus is trapped within the dialog.\n\n**`Esc`**\nDescription: Closes the dialog and moves focus to trigger or the defined final focus element","package":"@zag-js/dialog","editUrl":"https://github.com/chakra-ui/zag/edit/main/website/data/components/dialog.mdx"}