All questions

Tooltip Positioning Engine

Premium

Tooltip Positioning Engine

A tooltip near the top of the screen shouldn't open upward off-screen; one near the right edge shouldn't get clipped. Every real positioning library — Floating UI, Popper, the browser's own <select> dropdowns — solves the same geometry puzzle: place a floating box next to an anchor in a preferred direction, but flip to the opposite side when it won't fit, and shift it along the edge to stay in view. It's all rectangle math, which makes it a perfect pure function.

Implement tooltipPositioningEngine({ anchor, tooltip, viewport, placement, gap }). See Floating UI.

Signature

function tooltipPositioningEngine({ anchor, tooltip, viewport, placement, gap }) {
  return { x, y, placement }; // top-left corner + resolved placement
}

Examples

const anchor = { left: 440, top: 400, right: 520, bottom: 440, width: 80, height: 40 };
tooltipPositioningEngine({ anchor, tooltip: { width: 120, height: 60 }, viewport: { width: 1000, height: 800 }, placement: 'bottom', gap: 8 });
// { x: 420, y: 448, placement: 'bottom' }  — centered under the anchor
// anchor near the bottom edge with 'bottom' preferred -> flips to 'top'
// anchor near the left edge -> tooltip shifts right, clamped to x: 0

Notes

  • Main axis = the gap — for bottom/top the tooltip sits gap px below/above; for left/right, gap px to the side.
  • Cross axis = centered, then shifted — center the tooltip on the anchor, then slide it (and clamp) so it stays inside the viewport.
  • Flip — if the tooltip overflows on the preferred side but fits on the opposite side, use the opposite (top↔bottom, left↔right), and report the resolved placement.
  • Stay in bounds — the returned { x, y } must keep the whole tooltip within [0, viewport] on both axes (clamp as a last resort).

Unlock the solution & editor

  • Runnable editor + tests

    Solve in the browser with instant Jest feedback.

  • Detailed solutions

    Walkthroughs, edge cases, and complexity notes.

  • Multi-framework variants

    React, Vue, Vanilla, Angular — same question, different stacks.

Upgrade to Premium