Skip to main content
View Zag.js on Github
Join the Discord server


A tooltip is a brief, informative message that appears when a user interacts with an element. Tooltips are usually initiated when a button is focused or hovered.



  • Show tooltip on hover and focus.
  • Hide tooltip on esc or pointer down.
  • Only one tooltip shows at a time.
  • Labeling support for screen readers via aria-describedby.
  • Custom show and hide delay support.
  • Matches native tooltip behavior with delay on hover of first tooltip and no delay on subsequent tooltips.


To use the tooltip machine in your project, run the following command in your command line:

npm install @zag-js/tooltip @zag-js/react # or yarn add @zag-js/tooltip @zag-js/react

This command will install the framework agnostic tooltip logic and the reactive utilities for your framework of choice.


To set up the tooltip correctly, you'll need to understand its anatomy and how we name its parts.

Each part includes a data-part attribute to help identify them in the DOM.

On a high level, the tooltip consists of:

  • Trigger: The trigger for the tooltip.
  • Positioner: The element that dynamically positions the tooltip.
  • Arrow: The arrow that points to the trigger.
  • Content: The element that houses the content of the tooltip.


First, import the tooltip package into your project

import * as tooltip from "@zag-js/tooltip"

The tooltip package exports two key functions:

  • machine — The state machine logic for the tooltip widget.
  • connect — The function that translates the machine's state to JSX attributes and event handlers.

To get the tooltip working correct, you'll need to:

  • Setup the tooltip portal, this is a shared container for all tooltips
  • Add the triggerProps, and tooltipProps to the elements

You'll also need to provide a unique id to the useSetup hook. This is used to ensure that every part has a unique identifier.

Next, import the required hooks and functions for your framework and use the tooltip machine in your project 🔥

import * as tooltip from "@zag-js/tooltip" import { useMachine, normalizeProps } from "@zag-js/react" export function Tooltip() { const [state, send] = useMachine(tooltip.machine({ id: "1" })) const api = tooltip.connect(state, send, normalizeProps) return ( <> <button {...api.triggerProps}>Hover me</button> {api.isOpen && ( <div {...api.positionerProps}> <div {...api.contentProps}>Tooltip</div> </div> )} </> ) }

Customizing the timings

By default, the tooltip is designed to open after 1000ms and close after 500ms. You can customize this by passing the openDelay and closeDelay context properties.

const [state, send] = useMachine( tooltip.machine({ openDelay: 500, closeDelay: 200, }), )

Changing the placement

The tooltip uses floating-ui for dynamic positioning. You can change the placement of the tooltip by passing the positioning.placement context property to the machine.

const [state, send] = useMachine( tooltip.machine({ positioning: { placement: "bottom-start", }, }), )

You can configure other position-related properties in the positioning object. Here's what the positioning API looks like:

export type PositioningOptions = { /** * The strategy to use for positioning */ strategy?: "absolute" | "fixed" /** * The initial placement of the floating element */ placement?: Placement /** * The offset of the floating element */ offset?: { mainAxis?: number; crossAxis?: number } /** * The main axis offset or gap between the reference and floating elements */ gutter?: number /** * Whether to flip the placement */ flip?: boolean /** * Whether to make the floating element same width as the reference element */ sameWidth?: boolean /** * The overflow boundary of the reference element */ boundary?: Boundary /** * Options to activate auto-update listeners */ listeners?: boolean | AutoUpdateOptions }

Adding an arrow

To render an arrow within the tooltip, use the api.arrowProps and api.innerArrowProps.

//... const api = popover.connect(state, send) //... return ( <div {...api.positionerProps}> <div {...api.arrowProps}> <div {...api.innerArrowProps} /> </div> <div {...api.contentProps}>{/* ... */}</div> </div> ) //...

Pointerdown behavior

By default, the tooltip will close when the pointer is down on its trigger. To prevent this behavior, pass the closeOnPointerDown context property and set it to false.

const [state, send] = useMachine( tooltip.machine({ closeOnPointerDown: false, }), )

Closing on Esc

The tooltip is designed to close when the escape key is pressed. To prevent this, pass the closeOnEscape context property and set it to false.

const [state, send] = useMachine( tooltip.machine({ closeOnEsc: false, }), )

Making the tooltip interactive

Tooltips are interactive by default. That means it'll remain open even the pointer leaves the trigger and move into the tooltip's content.

To disabled this behavior, pass the interactive context property and set it to false.

Disabling this will violate the WCAG 2.1 success criterion 1.4.13

const [state, send] = useMachine( tooltip.machine({ interactive: false, }), )

Using custom labels

By default, the only content announced to them is whatever is in the tooltip. On some cases you may want the screen readers to know more information about the trigger (e.g. a notifications system with count).

For this use-case, pass the aria-label context property and use the labelProps exposed in the tooltip's connect function.

import { useMachine, normalizeProps } from "@zag-js/react" import * as tooltip from "@zag-js/tooltip" export function Tooltip() { const [state, send] = useMachine( tooltip.machine({ id: "1", "aria-label": "Tooltip Custom Label", }), ) const api = tooltip.connect(state, send, normalizeProps) return ( <> <button {...api.triggerProps}>Hover me</button> {api.isOpen && ( <div {...api.positionerProps}> <div {...api.contentProps}> {/* rendered */} <span>Tooltip</span> {/* announced */} <div {...api.labelProps} /> </div> </div> )} </> ) }


When the tooltip open or closes, the onOpen and onClose callbacks are fired.

const [state, send] = useMachine( tooltip.machine({ onOpen() { console.log("Tooltip opened") }, onClose() { console.log("Tooltip closed") }, }), )

Methods and Properties

The tooltip's api exposes the following methods:

  • isOpen — Whether the tooltip is open or not.
  • open() — Function used to programmatically open the tooltip.
  • close() — Function used to programmatically close the tooltip.
api.isOpen // => boolean api.close()

Styling guide

Earlier, we mentioned that each tooltip part has a data-part attribute added to them to select and style them in the DOM.

Open and close states

When the tooltip is open, the data-expanded attribute is added to the trigger

[data-part="trigger"][data-expanded] { /* styles for the trigger's expanded state */ }

Styling the tooltip

[data-part="content"] { /* styles for the content */ }

Styling the arrow

When using arrows within the menu, you can style it using css variables.

[data-part="arrow"] { --arrow-size: 20px; --arrow-background: red; }

Edit this page on GitHub

On this page