Tooltip
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.
Features
- Show tooltip on hover and focus
- Hide tooltip on Esc, click, pointer down, or scroll
- 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
Installation
Install the tooltip package:
npm install @zag-js/tooltip @zag-js/react # or yarn add @zag-js/tooltip @zag-js/react
npm install @zag-js/tooltip @zag-js/solid # or yarn add @zag-js/tooltip @zag-js/solid
npm install @zag-js/tooltip @zag-js/vue # or yarn add @zag-js/tooltip @zag-js/vue
npm install @zag-js/tooltip @zag-js/svelte # or yarn add @zag-js/tooltip @zag-js/svelte
Anatomy
Check the tooltip anatomy and part names.
Each part includes a
data-partattribute to help identify them in the DOM.
Usage
Import the tooltip package:
import * as tooltip from "@zag-js/tooltip"
The tooltip package exports two key functions:
machine- State machine logic.connect- Maps machine state to JSX props and event handlers.
To get tooltip working, you'll need to:
- Set up the tooltip portal (shared container for tooltips)
- Add
triggerPropsandtooltipPropsto the right elements
Then use the framework integration helpers:
import * as tooltip from "@zag-js/tooltip" import { useMachine, normalizeProps } from "@zag-js/react" export function Tooltip() { const service = useMachine(tooltip.machine, { id: "1" }) const api = tooltip.connect(service, normalizeProps) return ( <> <button {...api.getTriggerProps()}>Hover me</button> {api.open && ( <div {...api.getPositionerProps()}> <div {...api.getContentProps()}>Tooltip</div> </div> )} </> ) }
import * as tooltip from "@zag-js/tooltip" import { normalizeProps, useMachine } from "@zag-js/solid" import { createMemo, createUniqueId, Show } from "solid-js" export function Tooltip() { const service = useMachine(tooltip.machine, { id: createUniqueId() }) const api = createMemo(() => tooltip.connect(service, normalizeProps)) return ( <div> <button {...api().getTriggerProps()}>Hover me</button> <Show when={api().open}> <div {...api().getPositionerProps()}> <div {...api().getContentProps()}>Tooltip</div> </div> </Show> </div> ) }
<script setup> import * as tooltip from "@zag-js/tooltip" import { normalizeProps, useMachine } from "@zag-js/vue" import { computed } from "vue" const service = useMachine(tooltip.machine, { id: "1" }) const api = computed(() => tooltip.connect(service, normalizeProps)) </script> <template> <div> <button ref="ref" v-bind="api.getTriggerProps()">Hover me</button> <div v-if="api.open" v-bind="api.getPositionerProps()"> <div v-bind="api.getContentProps()">Tooltip</div> </div> </div> </template>
<script lang="ts"> import * as tooltip from "@zag-js/tooltip" import { useMachine, normalizeProps } from "@zag-js/svelte" const id = $props.id() const service = useMachine(tooltip.machine, { id }) const api = $derived(tooltip.connect(service, normalizeProps)) </script> <button {...api.getTriggerProps()}>Hover me</button> {#if api.open} <div {...api.getPositionerProps()}> <div {...api.getContentProps()}>Tooltip</div> </div> {/if}
Customizing the timings
By default, the tooltip opens after 400ms and closes after 150ms. You can
customize this by passing the openDelay and closeDelay context properties.
const service = 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 service = useMachine(tooltip.machine, { positioning: { placement: "bottom-start", }, })
Adding an arrow
To render an arrow within the tooltip, use the api.getArrowProps() and
api.getArrowTipProps().
//... const api = tooltip.connect(service, normalizeProps) //... return ( <div {...api.getPositionerProps()}> <div {...api.getArrowProps()}> <div {...api.getArrowTipProps()} /> </div> <div {...api.getContentProps()}>{/* ... */}</div> </div> ) //...
Dismiss behavior
Tooltips close on Escape, click, pointer down, and scroll by default. Configure
these with closeOnEscape, closeOnClick, closeOnPointerDown, and
closeOnScroll.
const service = useMachine(tooltip.machine, { closeOnEscape: false, closeOnClick: false, closeOnPointerDown: false, closeOnScroll: false, })
Making the tooltip interactive
Set the interactive context property to true to make the tooltip
interactive.
When a tooltip is interactive, it remains open as the pointer moves from the trigger into the content.
const service = useMachine(tooltip.machine, { interactive: true, })
Listening for open state changes
When the tooltip is opened or closed, the onOpenChange callback is invoked.
const service = useMachine(tooltip.machine, { onOpenChange(details) { // details => { open: boolean } console.log(details.open) }, })
Controlled tooltip
Use open and onOpenChange for controlled usage.
const service = useMachine(tooltip.machine, { open, onOpenChange(details) { setOpen(details.open) }, })
Programmatic open
Use the connected API for imperative control.
api.setOpen(true)
Styling guide
Each part includes a data-part attribute you can target in CSS.
[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.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 scrollcloseOnClickbooleanWhether the tooltip should close on clickinteractivebooleanWhether 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 contentdisabledbooleanWhether the tooltip is disabledopenbooleanThe controlled open state of the tooltipdefaultOpenbooleanThe initial open state of the tooltip when rendered. Use when you don't need to control the open state of the tooltip.dir"ltr" | "rtl"The document's text/writing direction.idstringThe unique identifier of the machine.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
CSS Variables
Accessibility
Keyboard Interactions
- TabOpens/closes the tooltip without delay.
- EscapeIf open, closes the tooltip without delay.