A cursor-following tooltip. Pointer position is tracked by writing --live-pointer-x/y to :root on every pointermove — CSS reads them directly, so the tooltip repositions on every move without triggering a React re-render. Visibility is a single boolean state. Edge flipping (right→left, above→below) is computed entirely in CSS using a max/min * 9999 conditional.

For transient value readouts: chart cursors, hover annotations, info icons. Not for surfacing partial detail in list or table cells — that belongs in the inspect flow.

Setup

Call once at app boot (idempotent):

function initLivePointer() {
  const root = document.documentElement;
  const writeViewport = () => {
    root.style.setProperty("--live-viewport-w", String(window.innerWidth));
    root.style.setProperty("--live-viewport-h", String(window.innerHeight));
  };
  writeViewport();
  window.addEventListener("resize", writeViewport, { passive: true });
  window.addEventListener("pointermove", (e) => {
    root.style.setProperty("--live-pointer-x", String(e.clientX));
    root.style.setProperty("--live-pointer-y", String(e.clientY));
  }, { passive: true });
}

PointerTooltip — wrapper form

Manages open state and portals to document.body to stay clear of aria-hidden sweeps inside dialogs:

<PointerTooltip content={<span>{value}</span>}>
  <button disabled aria-disabled="true">Hover me</button>
</PointerTooltip>

Disabled elements don’t fire pointer events in most browsers. Wrap in a <span> to intercept:

<PointerTooltip content="No permission to edit.">
  <span>
    <button disabled aria-disabled="true">Edit</button>
  </span>
</PointerTooltip>

PointerTooltipContent — bare shell

For surfaces that control their own open state and live inside a ModalBoundary (chart overlays):

<ModalBoundary className="relative h-64">
  <svg onPointerMove={handleMove} onPointerLeave={() => setHovered(false)}>
    {/* chart */}
  </svg>
  <PointerTooltipContent open={isHovered}>
    <div className="font-medium">{item.label}</div>
    <div className="text-xs text-muted">{format.number(item.value)}</div>
  </PointerTooltipContent>
</ModalBoundary>

Edge flip — CSS only

Boundary coordinates come from --bound-* (set by ModalBoundary) or fall back to --live-viewport-*:

roomRight = max(0px, rightLimit - preferred)
finalX    = max(leftLimit, min(preferred, flipped + roomRight * 9999))

When roomRight > 0, the * 9999 term dominates so min() picks preferred (right of cursor). When roomRight = 0, min() picks flipped (left of cursor). Same pattern on the Y axis.

Mount strategy

The portal element mounts on first hover and stays mounted. An unmounted node can’t run a CSS exit animation. role="tooltip" is set only while open, so screen readers and queryByRole("tooltip") only see it when visible.

Info icon pattern

<PointerTooltip
  content={<div className="max-w-xs text-xs">Calculated over the last 30 days.</div>}
>
  <InfoIcon className="size-4 text-muted cursor-default" />
</PointerTooltip>