Tooltips and popovers seem trivial until you implement them. The interview probes whether you understand viewport-aware positioning, accessibility (which differ for tooltips vs popovers), gesture handling, and the production patterns that make these elements feel polished.
Tooltip vs popover
- Tooltip: small text label on hover/focus. Non-interactive.
- Popover: richer floating UI on click/tap. May be interactive.
Different ARIA roles, different UX patterns.
Positioning
The hard part. Position must:
- Stay near the trigger element
- Stay inside the viewport (flip / shift if it would overflow)
- Stay relative to scroll
- Avoid covering the trigger
Library: Floating UI (formerly Popper.js) is the industry standard. Handles all positioning math.
Floating UI primitives
- autoUpdate: reposition on scroll, resize, content change
- offset: pixels from trigger
- flip: flip to other side if no room
- shift: shift along axis to stay in viewport
- arrow: position the arrow pointer
Tooltip implementation
Standard React pattern with Floating UI:
const { refs, floatingStyles } = useFloating({
middleware: [offset(8), flip(), shift()],
whileElementsMounted: autoUpdate
});
<button ref={refs.setReference} {...handlers}>Hover me</button>
{open && (
<div ref={refs.setFloating} style={floatingStyles}>Tooltip</div>
)}
Trigger events
For tooltips:
- onMouseEnter / onMouseLeave (hover)
- onFocus / onBlur (keyboard)
- Delay before showing (200–500ms) — avoid flash on transient hovers
- Hide immediately on leave
For popovers:
- onClick / onTap
- Click outside to dismiss
- Escape to dismiss
Hover delay
Without delay, tooltip flashes annoyingly when user moves cursor through. Standard:
- 200ms before show on hover
- 0ms before hide on leave
Cancel show timer if user leaves before delay completes.
Accessibility
Tooltip
aria-describedbyon the trigger pointing to the tooltip ID- Tooltip has
role="tooltip" - Trigger is naturally focusable (button) so keyboard users see it
- Don’t put interactive content in a tooltip
Popover
- Trigger has
aria-haspopup="dialog"andaria-expanded - Popover has
role="dialog" - Focus moves into popover on open
- Focus returns on close
- Trap focus inside popover (especially for forms)
Touch devices
Tooltips on touch are awkward — there is no hover. Patterns:
- Long-press to show
- Tap (then dismiss tap-elsewhere)
- Native title attribute (accessible, system-styled)
The HTML popover API
2024 native API:
<button popovertarget="my-popover">Open</button>
<div id="my-popover" popover>
Content
</div>
Browser handles light dismiss, focus trap, top layer rendering. Modern browsers support; older fallback to library.
Common mistakes
- Tooltip with interactive content (use popover)
- No flip/shift; tooltip overflows viewport
- No keyboard support (mouse-only)
- aria-describedby missing
- Tooltip persists after element scrolls off-screen
Frequently Asked Questions
Floating UI or Tippy.js?
Floating UI is the modern primitive. Tippy.js was built on Popper.js (now Floating UI). Use Floating UI for new projects.
Should I use the native HTML popover API?
For modern apps targeting recent browsers: yes. Some edge cases need library fallback.
How do I handle a tooltip on a disabled button?
Disabled buttons don’t fire mouse events. Wrap in a span with the trigger handlers, or use pointer-events: none on the button itself.