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.
| Prop | Type | Default | Description |
|---|---|---|---|
tooltipContent | ReactNode | — | 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. |
asChild | boolean | true | Merge trigger behavior onto the child element instead of wrapping it in a div. |
gap | number | 8 | Pixel distance between the trigger and the tooltip. |
open | boolean | — | 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. |
disableHoverListener | boolean | false | Disable opening/closing on mouse hover. |
disableFocusListener | boolean | false | Disable opening/closing on keyboard focus/blur. |
tooltipStyle | CSSProperties | — | Inline style applied to the tooltip element. backgroundColor also drives the arrow color. |
tooltipClassName | string | — | 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 getsaria-describedbypointing to it while visible. - Opens on keyboard focus and closes on blur unless
disableFocusListeneris 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
createPortalintodocument.body, so it escapes any container'soverflow/z-index. The portal is deferred until after the component mounts on the client, so it never renders during SSR. - If
tooltipContentis falsy (null,undefined,false,''),Tooltiprenders only the trigger — noaria-describedby, no tooltip markup, even while hovered. The trigger'srefand 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).