{"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.31.0](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\nTo use the dialog machine in your project, run the following command in your\ncommand line:\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\nFirst, import the dialog package into your project\n\n```jsx\nimport * as dialog from \"@zag-js/dialog\"\n```\n\nThe dialog package exports two key functions:\n\n- `machine` — The state machine logic for the dialog widget as described in\n  WAI-ARIA specification.\n- `connect` — The function that translates the machine's state to JSX attributes\n  and event handlers.\n\n> You'll need to provide a unique `id` to the `useMachine` hook. This is used to\n> ensure that every part has a unique identifier.\n\nNext, import the required hooks and functions for your framework and use the\ndialog machine in your project 🔥\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 automatically sets focus on the first focusable\nelements and traps focus within it, so that tabbing is constrained to it.\n\nTo control the element that should receive focus on open, pass the\n`initialFocusEl` context (which can be an element or a function that returns an\nelement)\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 set the element that receives focus when the dialog closes, pass the\n`finalFocusEl` in the similar fashion as shown above.\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` context 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### 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 the `preventScroll` context 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 has support for dialog and alert dialog roles. It's set to `dialog`\nby default. To change it's role, pass the `role: alertdialog` property to the\nmachine's context.\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> recommended setting focus to the least destructive action via `initialFocusEl`\n\n## Styling guide\n\nEarlier, we mentioned that each accordion part has a `data-part` attribute added\nto them to select and style them in the DOM.\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 look\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"}