{"slug":"hover-card","title":"Hover Card","description":"Using the hover-card machine in your project.","contentType":"component","framework":"react","content":"A hover card lets sighted users preview content behind a link.\n\n## Resources\n\n\n[Latest version: v2.0.0-next.2](https://www.npmjs.com/package/@zag-js/hover-card)\n[Logic Visualizer](https://zag-visualizer.vercel.app/hover-card)\n[Source Code](https://github.com/chakra-ui/zag/tree/v2/packages/machines/hover-card)\n\n\n\n**Features**\n\n- Customize side, alignment, offsets\n- Optionally render a pointing arrow\n- Supports custom open and close delays\n- Opens on hover or keyboard focus\n- Stays open while the pointer travels from the trigger to the content\n- Anchors to the hovered line when the trigger wraps across lines\n- Ignored by screen readers\n- Supports multiple triggers sharing a single hover card instance\n\n## Installation\n\nInstall the hover card package:\n\n```bash\nnpm install @zag-js/hover-card @zag-js/react\n# or\nyarn add @zag-js/hover-card @zag-js/react\n```\n\n## Anatomy\n\nTo set up the hover card correctly, you'll need to understand its anatomy and\nhow we name its parts.\n\n> Each part includes a component-scoped data attribute to help identify it in\n> the DOM.\n\n\n\n## Usage\n\nImport the hover card package:\n\n```tsx\nimport * as hoverCard from \"@zag-js/hover-card\"\n```\n\nThe hover card package exports two key functions:\n\n- `machine` - State machine logic.\n- `connect` - Maps machine state to JSX props and event handlers.\n\nThen use the framework integration helpers:\n\n```tsx\nimport * as hoverCard from \"@zag-js/hover-card\"\nimport { useMachine, normalizeProps, Portal } from \"@zag-js/react\"\nimport { useId } from \"react\"\n\nfunction HoverCard() {\n  const service = useMachine(hoverCard.machine, { id: useId() })\n\n  const api = hoverCard.connect(service, normalizeProps)\n\n  return (\n    <>\n      <a\n        href=\"https://twitter.com/zag_js\"\n        target=\"_blank\"\n        {...api.getTriggerProps()}\n      >\n        Twitter\n      </a>\n\n      {api.open && (\n        <Portal>\n          <div {...api.getPositionerProps()}>\n            <div {...api.getContentProps()}>\n              <div {...api.getArrowProps()}>\n                <div {...api.getArrowTipProps()} />\n              </div>\n              Twitter Preview\n            </div>\n          </div>\n        </Portal>\n      )}\n    </>\n  )\n}\n```\n\n### Setting the initial state\n\nSet `defaultOpen` to `true` to start with the hover card open.\n\n```tsx {2}\nconst service = useMachine(hoverCard.machine, {\n  defaultOpen: true,\n})\n```\n\n### Controlled open state\n\nUse `open` and `onOpenChange` to control visibility externally.\n\n```tsx\nconst service = useMachine(hoverCard.machine, {\n  open,\n  onOpenChange(details) {\n    setOpen(details.open)\n  },\n})\n```\n\n### Customizing open and close delays\n\nUse `openDelay` and `closeDelay` to control hover timing.\n\n```tsx\nconst service = useMachine(hoverCard.machine, {\n  openDelay: 300,\n  closeDelay: 150,\n})\n```\n\n### Positioning the hover card\n\nUse `positioning` to control placement and offsets.\n\n```tsx\nconst service = useMachine(hoverCard.machine, {\n  positioning: {\n    placement: \"bottom-start\",\n    offset: { mainAxis: 8, crossAxis: 4 },\n  },\n})\n```\n\n### Multiple triggers\n\nA single hover card instance can be shared across multiple trigger elements.\nPass a `value` to `getTriggerProps` to identify each trigger.\n\n```tsx\nconst users = [\n  { id: \"1\", name: \"Alice\", avatar: \"/alice.png\" },\n  { id: \"2\", name: \"Bob\", avatar: \"/bob.png\" },\n]\n\nconst service = useMachine(hoverCard.machine, {\n  onTriggerValueChange({ value }) {\n    const user = users.find((u) => u.id === value) ?? null\n    setActiveUser(user)\n  },\n})\n\nconst api = hoverCard.connect(service, normalizeProps)\n\nreturn (\n  <>\n    {users.map((user) => (\n      <a {...api.getTriggerProps({ value: user.id })}>{user.name}</a>\n    ))}\n    <div {...api.getPositionerProps()}>\n      <div {...api.getContentProps()}>\n        {/* Content updates based on activeUser */}\n      </div>\n    </div>\n  </>\n)\n```\n\nWhen hovering a different trigger while the card is open, it repositions without\nclosing.\n\n### Disabling the hover card\n\nSet `disabled` to `true` to prevent it from opening.\n\n```tsx\nconst service = useMachine(hoverCard.machine, {\n  disabled: true,\n})\n```\n\n### Listening for open state changes\n\nWhen the hover card is `opened` or `closed`, the `onOpenChange` callback is\ninvoked.\n\n```tsx {2-5}\nconst service = useMachine(hoverCard.machine, {\n  onOpenChange(details) {\n    // details => { open: boolean, reason?: OpenChangeReason }\n    console.log(\"hovercard is:\", details.open ? \"opened\" : \"closed\")\n  },\n})\n```\n\n`details.reason` says what caused the change, so you can treat a deliberate\ndismissal differently from the pointer wandering off.\n\n| Reason             | Meaning                                     |\n| ------------------ | ------------------------------------------- |\n| `trigger-hover`    | The pointer entered the trigger or content  |\n| `trigger-focus`    | The trigger received focus                  |\n| `trigger-blur`     | Focus left the trigger                      |\n| `pointer-leave`    | The pointer left the trigger and content    |\n| `interact-outside` | The user pressed outside the hover card     |\n| `escape-key`       | The user pressed `Esc`                      |\n| `script`           | Changed programmatically, such as `setOpen` |\n\n```tsx {3}\nconst service = useMachine(hoverCard.machine, {\n  onOpenChange(details) {\n    if (!details.open && details.reason === \"escape-key\") {\n      // the user dismissed it, so don't reopen on the next hover\n    }\n  },\n})\n```\n\n## Styling guide\n\nEach part includes a component-scoped data attribute you can target in CSS.\n\n```css\n[data-hover-card-trigger] {\n  /* styles for trigger */\n}\n\n[data-hover-card-content] {\n  /* styles for content */\n}\n```\n\n### Open and closed state\n\nThe hover card exposes a `data-state` attribute that can be used to style the\nhover card based on its open-close state.\n\n```css\n[data-hover-card-trigger][data-state=\"open|closed\"] {\n  /* styles for open or closed state */\n}\n\n[data-hover-card-content][data-state=\"open|closed\"] {\n  /* styles for open or closed state */\n}\n```\n\n### Arrow\n\nYou can use CSS variables to style the arrow.\n\n```css\n[data-hover-card-arrow] {\n  /* styles for arrow */\n  --arrow-background: white;\n  --arrow-size: 8px;\n}\n```\n\n## Methods and Properties\n\n### Machine Context\n\nThe hover card machine exposes the following context properties:\n\n**`ids`**\nType: `ElementIds | undefined`\nDescription: The ids of the elements in the popover. Useful for composition.\n\n**`onOpenChange`**\nType: `((details: OpenChangeDetails) => void) | undefined`\nDescription: Function called when the hover card opens or closes.\n\n**`openDelay`**\nType: `number | undefined`\nDescription: The duration from when the mouse enters the trigger until the hover card opens.\n\n**`closeDelay`**\nType: `number | undefined`\nDescription: The duration from when the mouse leaves the trigger or content until the hover card closes.\n\n**`disabled`**\nType: `boolean | undefined`\nDescription: Whether the hover card is disabled\n\n**`open`**\nType: `boolean | undefined`\nDescription: The controlled open state of the hover card\n\n**`defaultOpen`**\nType: `boolean | undefined`\nDescription: The initial open state of the hover card when rendered.\nUse when you don't need to control the open state of the hover card.\n\n**`positioning`**\nType: `PositioningOptions | undefined`\nDescription: The user provided options used to position the popover content\n\n**`triggerValue`**\nType: `string | null | undefined`\nDescription: The controlled trigger value\n\n**`defaultTriggerValue`**\nType: `string | null | undefined`\nDescription: The initial trigger value when rendered.\nUse when you don't need to control the trigger value.\n\n**`onTriggerValueChange`**\nType: `((details: TriggerValueChangeDetails) => void) | undefined`\nDescription: Function called when the trigger value changes.\n\n**`dir`**\nType: `\"ltr\" | \"rtl\" | undefined`\nDescription: The document's text/writing direction.\n\n**`id`**\nType: `string`\nDescription: The unique identifier of the machine.\n\n**`getRootNode`**\nType: `(() => ShadowRoot | Document | Node) | undefined`\nDescription: A root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.\n\n**`onPointerDownOutside`**\nType: `((event: PointerDownOutsideEvent) => void) | undefined`\nDescription: Function called when the pointer is pressed down outside the component\n\n**`onFocusOutside`**\nType: `((event: FocusOutsideEvent) => void) | undefined`\nDescription: Function called when the focus is moved outside the component\n\n**`onInteractOutside`**\nType: `((event: InteractOutsideEvent) => void) | undefined`\nDescription: Function called when an interaction happens outside the component\n\n### Machine API\n\nThe hover card `api` exposes the following methods:\n\n**`open`**\nType: `boolean`\nDescription: Whether the hover card is open\n\n**`setOpen`**\nType: `(open: boolean, reason?: OpenChangeReason) => void`\nDescription: Function to open the hover card\n\n**`triggerValue`**\nType: `string | null`\nDescription: The trigger value\n\n**`setTriggerValue`**\nType: `(value: string | null) => void`\nDescription: Function to set the trigger value\n\n**`reposition`**\nType: `(options?: Partial<PositioningOptions>) => void`\nDescription: Function to reposition the popover\n\n**`getTriggerState`**\nType: `(props?: TriggerProps) => TriggerState`\nDescription: Returns the state of a specific trigger, including whether it's the currently active one\n\n**`getPositionerState`**\nType: `() => PositionerState`\nDescription: Returns the state of the positioner\n\n**`getContentState`**\nType: `() => ContentState`\nDescription: Returns the state of the content\n\n### Data Attributes\n\n**`Trigger`**\n\n**`data-hover-card-trigger`**: <uid>\n**`data-placement`**: The placement of the trigger\n**`data-side`**: The side of the trigger that the trigger is positioned on\n**`data-value`**: The value of the item\n**`data-current`**: Present when current\n**`data-state`**: \"open\" | \"closed\"\n\n**`Content`**\n\n**`data-hover-card-content`**: <uid>\n**`data-state`**: \"open\" | \"closed\"\n**`data-placement`**: The placement of the content\n**`data-side`**: The side of the trigger that the content is positioned on\n**`data-nested`**: popover\n**`data-has-nested`**: popover\n\n### CSS Variables\n\n<CssVarTable name=\"hover-card\" />\n\n## Accessibility\n\nThe hover card is a progressive enhancement for sighted users. Its content is\nnot reachable by keyboard or exposed to screen readers, so treat it as a preview\nof something the user can already get to another way.\n\n> Keep the content non-essential and non-interactive. Anything the user must be\n> able to read or act on belongs on the page the trigger links to, not only in\n> the card.\n\n### Keyboard Interactions\n\nThe trigger is a normal focusable element. Focusing it opens the card so\nkeyboard users see the same preview, but focus never enters the content.\n\n**`Tab`**\nDescription: Opens the hover card once focus reaches the trigger, after the open delay. Closes it when focus leaves.\n\n**`Esc`**\nDescription: If open, closes the hover card.\n\nIf you need content the user can tab into, use a [popover](/components/popover)\ninstead.","package":"@zag-js/hover-card","editUrl":"https://github.com/chakra-ui/zag/edit/v2/website/data/components/hover-card.mdx"}