Skip to main content

Tooltip

A floating label that appears near a trigger element on hover or focus, positioned automatically relative to it.

Installation​

npm install @sipe-team/tooltip
import '@sipe-team/tooltip/styles.css';

Usage​

Wrap a trigger with Tooltip and give it tooltipContent. By default (asChild), the trigger behavior is merged onto the child element itself rather than wrapping it in an extra div.

Examples​

Placement​

placement sets where the tooltip appears relative to the trigger: top (default), top-left, top-right, bottom, bottom-left, bottom-right, left, or right.

Controlled visibility​

Pass open with onOpen/onClose to drive visibility from the parent. Combine with disableHoverListener and disableFocusListener to fully replace hover/focus triggering — here, a click toggles the tooltip instead.

Custom styling​

tooltipStyle and tooltipClassName customize the floating element; gap controls the pixel distance from the trigger. The arrow follows tooltipStyle.backgroundColor automatically.

asChild​

asChild defaults to true and merges the trigger behavior onto the child element, preserving its tag (here, an <h1>) instead of wrapping it in a div. Set asChild={false} to wrap the child in a plain div.

Hover me (h1 element)

Anatomy​

import { Tooltip } from '@sipe-team/tooltip';

export default () => (
<Tooltip tooltipContent="Helpful text" placement="top">
<button type="button">Hover me</button>
</Tooltip>
);

Renders children as the trigger (merging props onto it when asChild, or wrapping it in a div otherwise). While visible, a <div role="tooltip"> is portaled into document.body alongside it.

API Reference​

Renders the child element directly when asChild is true (default) or wraps it in a <div> otherwise, and forwards its ref to that trigger element. While open, also portals a <div role="tooltip"> into document.body.

PropTypeDefaultDescription
tooltipContentReactNode—Content shown inside the tooltip. If falsy, Tooltip renders only children — no tooltip markup at all.
placement'top' | 'top-left' | 'top-right' | 'bottom' | 'bottom-left' | 'bottom-right' | 'left' | 'right''top'Where the tooltip appears relative to the trigger.
asChildbooleantrueMerge trigger behavior onto the child element instead of wrapping it in a div.
gapnumber8Pixel distance between the trigger and the tooltip.
openboolean—Controls visibility externally. When set, pair it with onOpen/onClose.
onOpen() => void—Called when the tooltip requests to open (hover, focus, or click when listeners are disabled).
onClose() => void—Called when the tooltip requests to close. Hover/focus closes are delayed 150ms; outside click and Escape close immediately.
disableHoverListenerbooleanfalseDisable opening/closing on mouse hover.
disableFocusListenerbooleanfalseDisable opening/closing on keyboard focus/blur.
tooltipStyleCSSProperties—Inline style applied to the tooltip element. backgroundColor also drives the arrow color.
tooltipClassNamestring—Additional class name applied to the tooltip element.

Also accepts every other ComponentProps<'div'> (className, style, onClick, …), spread onto the trigger element.

Accessibility​

  • The floating element has role="tooltip", and the trigger gets aria-describedby pointing to it while visible.
  • Opens on keyboard focus and closes on blur unless disableFocusListener is set, so it is reachable by Tab.
  • Closes on outside click and on Escape, regardless of disableHoverListener / disableFocusListener.

Known limitations​

  • The tooltip is rendered via createPortal into document.body, so it escapes any container's overflow/z-index. The portal is deferred until after the component mounts on the client, so it never renders during SSR.
  • If tooltipContent is falsy (null, undefined, false, ''), Tooltip renders only the trigger — no aria-describedby, no tooltip markup, even while hovered. The trigger's ref and event handlers are still attached as usual.
  • Position is recalculated on scroll and resize (via requestAnimationFrame) only while the tooltip is visible; it does not observe unrelated layout shifts (for example, a sibling element resizing and pushing the trigger to a new position).