Skip to content

Headless Core — Tooltip

Powers Tooltip internally. Positioning (placing the tooltip near its anchor) is a real layout operation left entirely to the consuming component — this factory only tracks visibility and timing.

import { createTooltip } from '@andersseen/headless-components/tooltip';
const tooltip = createTooltip({
placement: 'top',
openDelay: 200,
closeDelay: 100,
onVisibilityChange: visible => console.log('Tooltip:', visible),
});
trigger.addEventListener('mouseenter', tooltip.handleMouseEnter);
trigger.addEventListener('mouseleave', tooltip.handleMouseLeave);
trigger.addEventListener('focusin', tooltip.handleFocusIn);
trigger.addEventListener('focusout', tooltip.handleFocusOut);
tooltip.destroy(); // clears any pending show/hide timer
OptionTypeDefaultNotes
placement'top' | 'bottom' | 'left' | 'right''top'
openDelaynumber (ms)0
closeDelaynumber (ms)0
onVisibilityChange(visible: boolean) => void
disabledbooleanfalse

isVisible, placement, disabled.

show() / hide() (both timer-debounced by openDelay/closeDelay — calling show() twice in a row just resets the pending timer, it doesn’t stack), setPlacement(v), setDisabled(v) (forces isVisible: false immediately if it was open). No queries bucket; read tooltip.state.isVisible directly.

GetterReturns
getTriggerProps()aria-describedby — only set to the tooltip’s id while visible, '' otherwise
getTooltipProps()role: 'tooltip', id, aria-hidden, data-state, data-side (the placement), hidden

handleMouseEnter/handleFocusIn call show(); handleMouseLeave/handleFocusOut call hide() — attach all four to the trigger element. destroy() clears any pending timer; call it when the trigger unmounts so a delayed show()/hide() doesn’t fire against a gone component.

Hover or focus me

Primitives — every other factory’s timer-free state pattern, for comparison.