Back

Tooltip

easy

Streak

0 days

Progress

0%

Submitted

0

Tooltip

React30 mineasyFreeNew

Prompt

Tooltips appear everywhere in production UIs — yet building one correctly is harder than it looks. A naive position: absolute tooltip gets clipped by any ancestor with overflow: hidden, breaks near viewport edges, and fails accessibility audits because screen readers can't discover the hidden text.

Build a reusable <Tooltip> component that accepts text and position (top/bottom/left/right) props and wraps any children. It should appear on hover and keyboard focus, be properly linked to the trigger via ARIA, and never get clipped by container overflow.

The architectural solution is a React Portal: render the tooltip into document.body and compute its position from getBoundingClientRect(). This completely sidesteps all overflow and stacking-context issues.

Requirements

  • →Show tooltip on hover AND keyboard focus
  • →Support position prop: top, bottom, left, right
  • →Tooltip must not get clipped by parent overflow:hidden
  • →aria-describedby links trigger to tooltip for screen readers
  • →Smooth fade-in/out transition
  • →Correct transform offsets so tooltip is centred on trigger
Example
Loading preview...
For the best coding experience, we recommend using a desktop device.
Preparing Sandbox...
Premium interview report

What interviewers score in this build

Use this before reading the code. It tells you what to say, what to test, and where machine-coding candidates usually lose points.

Interview signals

  • Portal usage: createPortal renders tooltip at body level, escaping overflow
  • getBoundingClientRect positioning: Viewport-relative coordinates, not DOM offsets
  • Multi-direction support: Correct x/y and transform for all 4 positions
  • ARIA accessibility: aria-describedby, role=tooltip, focus/blur handlers

Time checkpoints

  1. 1

    0–3 min: Explain overflow clipping and why Portal solves it

  2. 2

    3–8 min: Set up trigger ref and getBoundingClientRect calculation

  3. 3

    8–13 min: Implement createPortal with position:fixed

  4. 4

    13–17 min: Handle all 4 directions with transform math

Edge-case checklist

Empty data and first-load state
Slow network, failed request, and retry path
Keyboard navigation and focus movement
Large input size, re-render pressure, and cleanup

Common mistakes

  • Starting with JSX before naming state and events.
  • Ignoring accessibility until the final minute.
  • Over-building abstractions instead of finishing the required behavior.
  • Failing to narrate trade-offs while coding.
SolutionRead-only · Live Preview

Technical Explanation

Why This Question Exists

Tooltips are a perfect interview question because they look trivial but surface real engineering challenges: overflow clipping, scroll-offset positioning, keyboard accessibility, and the Portal escape hatch. A candidate who just uses position: absolute fails the overflow test. A candidate who uses a Portal but forgets aria-describedby fails the accessibility audit.

The Wrong Approach: position: absolute

// ❌ WRONG — gets clipped by overflow:hidden parents
function Tooltip({ text, children }) {
  const [show, setShow] = useState(false);
  return (
    <span style={{ position: 'relative' }}
      onMouseEnter={() => setShow(true)}
      onMouseLeave={() => setShow(false)}>
      {children}
      {show && <div style={{ position: 'absolute', top: '-30px' }}>{text}</div>}
    </span>
  );
}

This fails the moment any ancestor has overflow: hidden — the tooltip is clipped at the container boundary. In a real product, cards, modals, and sidebars all have overflow: hidden.

The Right Approach: React Portal

// ✅ CORRECT — Portal escapes all overflow containers
import { createPortal } from 'react-dom';

{coords && createPortal(
  <div id={id} role="tooltip" style={{
    position: 'fixed',
    left: coords.x,
    top: coords.y,
    transform: TRANSFORMS[coords.position]
  }}>
    {text}
  </div>,
  document.body  // mounted directly on body — no overflow parent
)}

createPortal renders the tooltip as a direct child of document.body in the real DOM, while keeping it in the React component tree. This means React events, refs, and context all still work, but the tooltip is visually outside every ancestor container.

Position Calculation with getBoundingClientRect

const show = () => {
  const r = triggerRef.current.getBoundingClientRect();
  // r gives viewport-relative coordinates — works with scroll!
  const positions = {
    top:    { x: r.left + r.width / 2,  y: r.top - OFFSET },
    bottom: { x: r.left + r.width / 2,  y: r.bottom + OFFSET },
    left:   { x: r.left - OFFSET,        y: r.top + r.height / 2 },
    right:  { x: r.right + OFFSET,       y: r.top + r.height / 2 },
  };
  setCoords({ ...positions[position], position });
};

Using getBoundingClientRect() is crucial. It returns coordinates relative to the viewport, not the document. Since we use position: fixed on the tooltip, these coordinates map directly to screen position — scrolling doesn't break anything.

Transform Offsets — the maths

const TRANSFORMS = {
  top:    'translateX(-50%) translateY(-100%)',
  // x is the centre of the trigger, so pull left by 50% of tooltip width
  // y is the top of the trigger, so pull up by 100% of tooltip height

  bottom: 'translateX(-50%)',
  // x same, y is already at the bottom edge — no vertical shift needed

  left:   'translateX(-100%) translateY(-50%)',
  // x is the left edge of trigger, pull left by 100% of tooltip width
  // y is the vertical centre, pull up by 50% of tooltip height

  right:  'translateY(-50%)',
  // x is the right edge — no horizontal shift
  // y same as left
};
ℹ Interview Tip

Say: "I position the tooltip using position:fixed at viewport coordinates from getBoundingClientRect(). Then CSS transforms align it relative to the tooltip's own dimensions — because we don't know the tooltip's pixel size until it renders, transforms are the only reliable way to centre it."

Accessibility — the Full Picture

const id = useId(); // stable unique ID

<span
  aria-describedby={coords ? id : undefined}
  onFocus={show}   // keyboard users
  onBlur={hide}    // keyboard users
>
  {children}
</span>

<div id={id} role="tooltip">{text}</div>

aria-describedby connects the trigger to the tooltip via ID. When the trigger is focused, screen readers announce "button text, [tooltip text]". Without this, keyboard users and screen reader users get no tooltip content. role="tooltip" tells the assistive tech what kind of element this is.

⚠ Common Pitfall: Tooltip flickers on Portal hover

If the tooltip overlaps the trigger (e.g., position bottom with a very short offset), the mouse enters the tooltip, triggering mouseLeave on the trigger, hiding the tooltip, which makes the mouse re-enter the trigger... causing a flicker loop. Fix: add pointer-events: none to the tooltip so it doesn't intercept mouse events.

Interview Criteria

Portal usage

createPortal renders tooltip at body level, escaping overflow

getBoundingClientRect positioning

Viewport-relative coordinates, not DOM offsets

Multi-direction support

Correct x/y and transform for all 4 positions

ARIA accessibility

aria-describedby, role=tooltip, focus/blur handlers

Smooth transition

Opacity fade rather than abrupt show/hide

Time Checkpoints

0–3 min

0–3 min: Explain overflow clipping and why Portal solves it

3–8 min

3–8 min: Set up trigger ref and getBoundingClientRect calculation

8–13 min

8–13 min: Implement createPortal with position:fixed

13–17 min

13–17 min: Handle all 4 directions with transform math

17–21 min

17–21 min: Add aria-describedby and focus/blur handlers

21–25 min

21–25 min: Add fade transition, test near viewport edges

Streak

0 days

Last active: Sign in to track

Progress

0%

0/0 solved

Submitted

0

Solutions pushed to review history.