Skip to main content

Tooltip

A hover/focus hint with delay, skip-delay, and grace area support.

import * as Tooltip from '@gentleduck/primitives/tooltip'
import * as Tooltip from '@gentleduck/primitives/tooltip'

Anatomy

<Tooltip.Provider>
  <Tooltip.Root>
    <Tooltip.Trigger />
    <Tooltip.Portal>
      <Tooltip.Content>
        <Tooltip.Arrow />
      </Tooltip.Content>
    </Tooltip.Portal>
  </Tooltip.Root>
</Tooltip.Provider>
<Tooltip.Provider>
  <Tooltip.Root>
    <Tooltip.Trigger />
    <Tooltip.Portal>
      <Tooltip.Content>
        <Tooltip.Arrow />
      </Tooltip.Content>
    </Tooltip.Portal>
  </Tooltip.Root>
</Tooltip.Provider>

Example

import * as Tooltip from '@gentleduck/primitives/tooltip'
 
function App() {
  return (
    <Tooltip.Provider delayDuration={400}>
      <Tooltip.Root>
        <Tooltip.Trigger className="px-3 py-1 border rounded">
          Hover me
        </Tooltip.Trigger>
        <Tooltip.Portal>
          <Tooltip.Content
            className="bg-gray-900 text-white text-sm px-3 py-1.5 rounded shadow"
            sideOffset={5}
          >
            This is a tooltip
            <Tooltip.Arrow className="fill-gray-900" />
          </Tooltip.Content>
        </Tooltip.Portal>
      </Tooltip.Root>
    </Tooltip.Provider>
  )
}
import * as Tooltip from '@gentleduck/primitives/tooltip'
 
function App() {
  return (
    <Tooltip.Provider delayDuration={400}>
      <Tooltip.Root>
        <Tooltip.Trigger className="px-3 py-1 border rounded">
          Hover me
        </Tooltip.Trigger>
        <Tooltip.Portal>
          <Tooltip.Content
            className="bg-gray-900 text-white text-sm px-3 py-1.5 rounded shadow"
            sideOffset={5}
          >
            This is a tooltip
            <Tooltip.Arrow className="fill-gray-900" />
          </Tooltip.Content>
        </Tooltip.Portal>
      </Tooltip.Root>
    </Tooltip.Provider>
  )
}

API

Tooltip.Provider

Wraps your app (or section) to coordinate tooltip delays. When a user moves from one tooltip trigger to another quickly, the second tooltip opens instantly (skip delay).

PropTypeDefaultDescription
delayDurationnumber700Delay in ms before tooltip opens
skipDelayDurationnumber300Time in ms before delay resets after closing
disableHoverableContentbooleanfalseClose immediately when pointer leaves trigger (can't hover content)

Tooltip.Root

Manages state for a single tooltip instance.

PropTypeDefaultDescription
openboolean-Controlled open state
defaultOpenbooleanfalseInitial open state
onOpenChange(open: boolean) => void-Called on state change
delayDurationnumber-Override Provider's delay for this tooltip
disableHoverableContentboolean-Override Provider's setting

Tooltip.Trigger

The element that activates the tooltip on hover/focus.

PropTypeDefaultDescription
disableCloseOnClickbooleanfalseWhen true, clicking the trigger does not dismiss the tooltip. Use when wrapping interactive elements like Toggle with asChild.

When disableCloseOnClick is enabled:

  • The tooltip's data-slot and data-state are not set on the trigger, so the child element's own attributes pass through unchanged
  • The tooltip's onClick handler is not attached, so the child's onClick works without interference
  • onPointerDown skips tooltip close
  • A data-tooltip-state attribute is set instead for tooltip-specific styling if needed
  • Hover and blur behavior remain unchanged
import * as Tooltip from '@gentleduck/primitives/tooltip'
import { Toggle } from '@gentleduck/primitives/toggle'
 
<Tooltip.Root>
  <Tooltip.Trigger asChild disableCloseOnClick>
    <Toggle>Bold</Toggle>
  </Tooltip.Trigger>
  <Tooltip.Portal>
    <Tooltip.Content>Toggle bold formatting</Tooltip.Content>
  </Tooltip.Portal>
</Tooltip.Root>
import * as Tooltip from '@gentleduck/primitives/tooltip'
import { Toggle } from '@gentleduck/primitives/toggle'
 
<Tooltip.Root>
  <Tooltip.Trigger asChild disableCloseOnClick>
    <Toggle>Bold</Toggle>
  </Tooltip.Trigger>
  <Tooltip.Portal>
    <Tooltip.Content>Toggle bold formatting</Tooltip.Content>
  </Tooltip.Portal>
</Tooltip.Root>

Tooltip.Portal

Portals content to document.body.

Tooltip.Content

The floating tooltip content. Positioned by the Popper engine.

PropTypeDefaultDescription
aria-labelstring-Accessible label when content is purely visual
side'top' | 'right' | 'bottom' | 'left''top'Preferred side
sideOffsetnumber0Distance from anchor in pixels
align'start' | 'center' | 'end''center'Alignment along the side
alignOffsetnumber0Alignment offset in pixels
forceMounttrue-Keep mounted always
onEscapeKeyDown(event) => void-Called on Escape press
onPointerDownOutside(event) => void-Called on outside click

Tooltip.Arrow

Optional visual arrow pointing toward the trigger.


Data attributes


Grace area

When the user moves their pointer from the trigger toward the content, a grace area (triangle) keeps the tooltip open during the transition. This prevents the tooltip from closing when the user moves diagonally to interact with the content.

Set disableHoverableContent={true} to disable this behavior.


Keyboard interactions

KeyAction
TabFocus trigger, opens tooltip
EscapeClose tooltip