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.


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.getTriggerProps()}>Hover me</button> { && ( <div {...api.getPositionerProps()}> <div {...api.getContentProps()}>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.getArrowProps() and api.getArrowTipProps().

//... const api = popover.connect(state, send) //... return ( <div {...api.getPositionerProps()}> <div {...api.getArrowProps()}> <div {...api.getArrowTipProps()} /> </div> <div {...api.getContentProps()}>{/* ... */}</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

Set the interactive context property to true to make them interactive.

When a tooltip is interactive, it'll remain open even the pointer leaves the trigger and move into the tooltip's content.

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

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( }, }), )

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

Machine Context

The tooltip machine exposes the following context properties:

  • idsPartial<{ trigger: string; content: string; arrow: string; positioner: string; }>The ids of the elements in the tooltip. Useful for composition.
  • idstringThe `id` of the tooltip.
  • openDelaynumberThe open delay of the tooltip.
  • closeDelaynumberThe close delay of the tooltip.
  • closeOnPointerDownbooleanWhether to close the tooltip on pointerdown.
  • closeOnEscapebooleanWhether to close the tooltip when the Escape key is pressed.
  • closeOnScrollbooleanWhether the tooltip should close on scroll
  • interactivebooleanWhether the tooltip's content is interactive. In this mode, the tooltip will remain open when user hovers over the content.
  • onOpenChange(details: OpenChangeDetails) => voidFunction called when the tooltip is opened.
  • aria-labelstringCustom label for the tooltip.
  • positioningPositioningOptionsThe user provided options used to position the popover content
  • disabledbooleanWhether the tooltip is disabled
  • openbooleanWhether the tooltip is open
  • open.controlledbooleanWhether the tooltip is controlled by the user
  • dir"ltr" | "rtl"The document's text/writing direction.
  • getRootNode() => ShadowRoot | Node | DocumentA root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.

Machine API

The tooltip api exposes the following methods:

  • openbooleanWhether the tooltip is open.
  • setOpen(open: boolean) => voidFunction to open the tooltip.
  • reposition(options?: Partial<PositioningOptions>) => voidFunction to reposition the popover

Data Attributes

Present when expanded
"open" | "closed"
"open" | "closed"
The placement of the content


Keyboard Interactions

  • Tab
    Opens/closes the tooltip without delay.
  • Escape
    If open, closes the tooltip without delay.

Edit this page on GitHub

Proudly made in🇳🇬by Segun Adebayo

Copyright © 2024
On this page