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 useMachine 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.arrowTipProps.

//... const api = popover.connect(state, send) //... return ( <div {...api.positionerProps}> <div {...api.arrowProps}> <div {...api.arrowTipProps} /> </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, }), )

Listening for open state changes

When the tooltip is opened or closed, the onOpenChange callback is invoked.

const [state, send] = useMachine( tooltip.machine({ onOpenChange(details) { // details => { open: boolean } console.log("Tooltip", }, }), )

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.

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

Open and close states

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

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

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; }

Methods and Properties

The tooltip's api exposes the following methods:

  • isOpenbooleanWhether the tooltip is open.
  • open() => voidFunction to open the tooltip.
  • close() => voidFunction to close the tooltip.
  • setPositioning(options?: Partial<PositioningOptions>) => voidFunction to reposition the popover

Edit this page on GitHub

On this page