Skip to Content
ComponentsTooltip

Tooltip

Hover- and focus-activated tooltip with configurable delay and skip-delay window for sibling tooltips.

Anatomy

import { Tooltip } from "@spacing-ui/core"; <Tooltip.Provider delayDuration={700} skipDelayDuration={300}> <Tooltip> <Tooltip.Trigger> <button>Hover me</button> </Tooltip.Trigger> <Tooltip.Content>I am a tooltip</Tooltip.Content> </Tooltip> </Tooltip.Provider>;

Tooltip.Trigger clones its child and injects event handlers + aria-describedby — bring your own button.

API

Tooltip.Provider

PropTypeDefaultDescription
delayDurationnumber700ms before opening on hover
skipDelayDurationnumber300ms after one closes during which the next opens immediately

Tooltip (Root)

PropTypeDescription
open / defaultOpenboolean
onOpenChange(open: boolean) => void
delayDurationnumberPer-instance override of provider delay

Accessibility

  • Content has role="tooltip"
  • Trigger gets aria-describedby pointing to content when open
  • Escape closes the tooltip

Examples

Icon buttons

Tooltips make icon-only buttons discoverable without adding visible labels:

<Tooltip> <Tooltip.Trigger> <button aria-label="Delete"><TrashIcon /></button> </Tooltip.Trigger> <Tooltip.Content>Delete</Tooltip.Content> </Tooltip>

The tooltip’s content is announced via aria-describedby, not aria-labelledby — keep the button’s aria-label.

Provider setup

Wrap your app once in Tooltip.Provider to share delay + skip-delay across every tooltip:

export default function RootLayout({ children }) { return ( <Tooltip.Provider delayDuration={500} skipDelayDuration={200}> {children} </Tooltip.Provider> ); }

Individual tooltips can still override via the per-instance delayDuration prop.

Live demo

Testing

Tooltips open on hover and on focus, so tests can drive them via the keyboard:

const trigger = screen.getByRole("button", { name: /hover me/i }); trigger.focus(); expect(await screen.findByRole("tooltip")).toHaveTextContent(/i am a tooltip/i);
Last updated on