{"slug":"popover","title":"Popover","description":"Using the popover machine in your project.","contentType":"component","framework":"react","content":"A popover is a non-modal dialog that floats around a trigger. It is used to\ndisplay contextual information to the user, and should be paired with a\nclickable trigger element.\n\n## Resources\n\n\n[Latest version: v1.31.0](https://www.npmjs.com/package/@zag-js/popover)\n[Logic Visualizer](https://zag-visualizer.vercel.app/popover)\n[Source Code](https://github.com/chakra-ui/zag/tree/main/packages/machines/popover)\n\n\n\n**Features**\n\n- Focus is managed and can be customized\n- Supports modal and non-modal modes\n- Ensures correct DOM order after tabbing out of the popover, whether it's\n  portalled or not\n\n## Installation\n\nTo use the popover machine in your project, run the following command in your\ncommand line:\n\n```bash\nnpm install @zag-js/popover @zag-js/react\n# or\nyarn add @zag-js/popover @zag-js/react\n```\n\n## Anatomy\n\nTo set up the popover correctly, you'll need to understand its anatomy and how\nwe 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 popover package into your project\n\n```jsx\nimport * as popover from \"@zag-js/popover\"\n```\n\nThe popover package exports two key functions:\n\n- `machine` — The state machine logic for the popover widget.\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\npopover machine in your project 🔥\n\n```jsx\nimport { useId } from \"react\"\nimport * as popover from \"@zag-js/popover\"\nimport { useMachine, normalizeProps, Portal } from \"@zag-js/react\"\n\nexport function Popover() {\n  const service = useMachine(popover.machine, { id: useId() })\n\n  const api = popover.connect(service, normalizeProps)\n\n  const Wrapper = api.portalled ? Portal : React.Fragment\n\n  return (\n    <div>\n      <button {...api.getTriggerProps()}>Click me</button>\n      <Wrapper>\n        <div {...api.getPositionerProps()}>\n          <div {...api.getContentProps()}>\n            <div {...api.getTitleProps()}>Presenters</div>\n            <div {...api.getDescriptionProps()}>Description</div>\n            <button>Action Button</button>\n            <button {...api.getCloseTriggerProps()}>X</button>\n          </div>\n        </div>\n      </Wrapper>\n    </div>\n  )\n}\n```\n\n### Rendering the popover in a portal\n\nBy default, the popover is rendered in the same DOM hierarchy as the trigger. To\nrender the popover within a portal, pass `portalled: true` property to the\nmachine's context.\n\n> Note: This requires that you render the component within a `Portal` based on\n> the framework you use.\n\n```jsx\nimport * as popover from \"@zag-js/popover\"\nimport { useMachine, normalizeProps, Portal } from \"@zag-js/react\"\nimport * as React from \"react\"\n\nexport function Popover() {\n  const service = useMachine(popover.machine, { id: \"1\" })\n\n  const api = popover.connect(service, normalizeProps)\n\n  return (\n    <div>\n      <button {...api.getTriggerProps()}>Click me</button>\n      <Portal>\n        <div {...api.getPositionerProps()}>\n          <div {...api.getContentProps()}>\n            <div {...api.getTitleProps()}>Presenters</div>\n            <div {...api.getDescriptionProps()}>Description</div>\n            <button>Action Button</button>\n            <button {...api.getCloseTriggerProps()}>X</button>\n          </div>\n        </div>\n      </Portal>\n    </div>\n  )\n}\n```\n\n### Managing focus within popover\n\nWhen the popover open, focus is automatically set to the first focusable element\nwithin the popover. To customize the element that should get focus, set the\n`initialFocusEl` property in the machine's context.\n\n```jsx {3,7,14}\nexport function Popover() {\n  // initial focused element ref\n  const inputRef = useRef(null)\n\n  const service = useMachine(popover.machine, {\n    id: \"1\",\n    initialFocusEl: () => inputRef.current,\n  })\n\n  // ...\n\n  return (\n    //...\n    <input ref={inputRef} />\n    // ...\n  )\n}\n```\n\n### Changing the modality\n\nIn some cases, you might want the popover to be **modal**. This means that\nit'll:\n\n- trap focus within its content\n- block scrolling on the `body`\n- disable pointer interactions outside the popover\n- hide content behind the popover from screen readers\n\nTo make the popover modal, set the `modal: true` property in the machine's\ncontext. When `modal: true`, we set the `portalled` attribute to `true` as well.\n\n> **Note**: This requires that you render the component within a `Portal`.\n\n```jsx {2}\nconst service = useMachine(popover.machine, {\n  modal: true,\n})\n```\n\n### Close behavior\n\nThe popover is designed to close on blur and when the `esc` key is pressed.\n\nTo prevent it from closing on blur (clicking or focusing outside), pass the\n`closeOnInteractOutside` property and set it to `false`.\n\n```jsx {2}\nconst service = useMachine(popover.machine, {\n  closeOnInteractOutside: true,\n})\n```\n\nTo prevent it from closing when the `esc` key is pressed, pass the\n`closeOnEscape` property and set it to `false`.\n\n```jsx {2}\nconst service = useMachine(popover.machine, {\n  closeOnEscape: true,\n})\n```\n\n### Adding an arrow\n\nTo render an arrow within the popover, use the `api.getArrowProps()` and\n`api.getArrowTipProps()`.\n\n```jsx {6-8}\n//...\nconst api = popover.connect(service, normalizeProps)\n//...\nreturn (\n  <div {...api.getPositionerProps()}>\n    <div {...api.getContentProps()}>\n      <div {...api.getArrowProps()}>\n        <div {...api.getArrowTipProps()} />\n      </div>\n      //...\n    </div>\n  </div>\n)\n//...\n```\n\n### Changing the placement\n\nTo change the placement of the popover, set the `positioning.placement` property\nin the machine's context.\n\n```jsx {2-4}\nconst service = useMachine(popover.machine, {\n  positioning: {\n    placement: \"top-start\",\n  },\n})\n```\n\n### Listening for open state changes\n\nWhen the popover is opened or closed, the `onOpenChange` callback is invoked.\n\n```jsx {2-7}\nconst service = useMachine(popover.machine, {\n  onOpenChange(details) {\n    // details => { open: boolean }\n    console.log(\"Popover\", details.open)\n  },\n})\n```\n\n### Usage within dialog\n\nWhen using the popover within a dialog, avoid rendering the popover in a\n`Portal` or `Teleport`. This is because the dialog will trap focus within it,\nand the popover will be rendered outside the dialog.\n\n## Styling guide\n\nEarlier, we mentioned that each popover part has a `data-part` attribute added\nto them to select and style them in the DOM.\n\n### Open and closed state\n\nWhen the popover is expanded, we add a `data-state` and `data-placement`\nattribute to the trigger.\n\n```css\n[data-part=\"trigger\"][data-state=\"open|closed\"] {\n  /* styles for the expanded state */\n}\n\n[data-part=\"content\"][data-state=\"open|closed\"] {\n  /* styles for the expanded state */\n}\n\n[data-part=\"trigger\"][data-placement=\"(top|bottom)-(start|end)\"] {\n  /* styles for computed placement */\n}\n```\n\n### Position aware\n\nWhen the popover is expanded, we add a `data-state` and `data-placement`\nattribute to the trigger.\n\n```css\n[data-part=\"trigger\"][data-placement=\"(top|bottom)-(start|end)\"] {\n  /* styles for computed placement */\n}\n\n[data-part=\"content\"][data-placement=\"(top|bottom)-(start|end)\"] {\n  /* styles for computed placement */\n}\n```\n\n### Arrow\n\nThe arrow element requires specific css variables to be set for it to show\ncorrectly.\n\n```css\n[data-part=\"arrow\"] {\n  --arrow-background: white;\n  --arrow-size: 16px;\n}\n```\n\nA common technique for adding a shadow to the arrow is to use set\n`filter: drop-down(...)` css property on the content element. Alternatively, you\ncan use the `--arrow-shadow-color` variable.\n\n```css\n[data-part=\"arrow\"] {\n  --arrow-shadow-color: gray;\n}\n```\n\n## Methods and Properties\n\n### Machine Context\n\nThe popover machine exposes the following context properties:\n\n**`ids`**\nType: `Partial<{ anchor: string; trigger: string; content: string; title: string; description: string; closeTrigger: string; positioner: string; arrow: string; }>`\nDescription: The ids of the elements in the popover. Useful for composition.\n\n**`modal`**\nType: `boolean`\nDescription: Whether the popover should be modal. When set to `true`:\n- interaction with outside elements will be disabled\n- only popover content will be visible to screen readers\n- scrolling is blocked\n- focus is trapped within the popover\n\n**`portalled`**\nType: `boolean`\nDescription: Whether the popover is portalled. This will proxy the tabbing behavior regardless of the DOM position\nof the popover content.\n\n**`autoFocus`**\nType: `boolean`\nDescription: Whether to automatically set focus on the first focusable\ncontent within the popover when opened.\n\n**`initialFocusEl`**\nType: `() => HTMLElement`\nDescription: The element to focus on when the popover is opened.\n\n**`closeOnInteractOutside`**\nType: `boolean`\nDescription: Whether to close the popover when the user clicks outside of the popover.\n\n**`closeOnEscape`**\nType: `boolean`\nDescription: Whether to close the popover when the escape key is pressed.\n\n**`onOpenChange`**\nType: `(details: OpenChangeDetails) => void`\nDescription: Function invoked when the popover opens or closes\n\n**`positioning`**\nType: `PositioningOptions`\nDescription: The user provided options used to position the popover content\n\n**`open`**\nType: `boolean`\nDescription: The controlled open state of the popover\n\n**`defaultOpen`**\nType: `boolean`\nDescription: The initial open state of the popover when rendered.\nUse when you don't need to control the open state of the popover.\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**`dir`**\nType: `\"ltr\" | \"rtl\"`\nDescription: The document's text/writing direction.\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 popover `api` exposes the following methods:\n\n**`portalled`**\nType: `boolean`\nDescription: Whether the popover is portalled.\n\n**`open`**\nType: `boolean`\nDescription: Whether the popover is open\n\n**`setOpen`**\nType: `(open: boolean) => void`\nDescription: Function to open or close the popover\n\n**`reposition`**\nType: `(options?: Partial<PositioningOptions>) => void`\nDescription: Function to reposition the popover\n\n### Data Attributes\n\n**`Trigger`**\n\n**`data-scope`**: popover\n**`data-part`**: trigger\n**`data-placement`**: The placement of the trigger\n**`data-state`**: \"open\" | \"closed\"\n\n**`Indicator`**\n\n**`data-scope`**: popover\n**`data-part`**: indicator\n**`data-state`**: \"open\" | \"closed\"\n\n**`Content`**\n\n**`data-scope`**: popover\n**`data-part`**: content\n**`data-state`**: \"open\" | \"closed\"\n**`data-nested`**: popover\n**`data-has-nested`**: popover\n**`data-expanded`**: Present when expanded\n**`data-placement`**: The placement of the content\n\n### CSS Variables\n\n<CssVarTable name=\"popover\" />\n\n## Accessibility\n\nAdheres to the\n[Dialog WAI-ARIA design pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialogmodal).\n\n### Keyboard Interactions\n\n**`Space`**\nDescription: Opens/closes the popover.\n\n**`Enter`**\nDescription: Opens/closes the popover.\n\n**`Tab`**\nDescription: <span>Moves focus to the next focusable element within the content.<br /><strong>Note:</strong> If there are no focusable elements, focus is moved to the next focusable element after the trigger.</span>\n\n**`Shift + Tab`**\nDescription: <span>Moves focus to the previous focusable element within the content<br /><strong>Note:</strong> If there are no focusable elements, focus is moved to the trigger.</span>\n\n**`Esc`**\nDescription: <span>Closes the popover and moves focus to the trigger.</span>","package":"@zag-js/popover","editUrl":"https://github.com/chakra-ui/zag/edit/main/website/data/components/popover.mdx"}