Tooltip

Contextual labels that appear on hover and focus. Portaled to document.body, auto-flips when near viewport edges, and dismisses instantly on mouse-out.

Preview

Placement

Four sides, each with automatic flip detection. If the preferred side would overflow the viewport the tooltip moves to the opposite side. Horizontal position is always clamped within the safe area.

Behavior

TriggerActionDefault delay
mouseenterShow after delay400ms
mouseleaveHide immediately
focusShow after delay400ms
blurHide immediately

Implementation

The tooltip panel is portaled to document.body so it is never clipped by overflow: hidden ancestors. Position is calculated after mount using getBoundingClientRect.

Tooltip.tsx
tsx
import { Tooltip } from "@/components/ui/Tooltip";
import { Button } from "@/components/ui/Button";

// Basic usage
<Tooltip content="Copy to clipboard">
  <Button variant="ghost" size="icon" aria-label="Copy">
    <Copy className="w-4 h-4" />
  </Button>
</Tooltip>

// Placement — flips automatically if it overflows
<Tooltip content="Top tooltip"    side="top">    <Button>Top</Button>    </Tooltip>
<Tooltip content="Bottom tooltip" side="bottom"> <Button>Bottom</Button> </Tooltip>
<Tooltip content="Left tooltip"   side="left">   <Button>Left</Button>   </Tooltip>
<Tooltip content="Right tooltip"  side="right">  <Button>Right</Button>  </Tooltip>

// Rich content
<Tooltip content={
  <span>
    Press <kbd className="font-mono">⌘K</kbd> to open
  </span>
}>
  <Button variant="ghost">Search</Button>
</Tooltip>

// Custom delay
<Tooltip content="Appears after 100ms" delay={100}>
  <Button variant="secondary">Fast</Button>
</Tooltip>

Props

PropTypeDefaultDescription
contentReactNodeTooltip label. Accepts strings or any React node.
side"top" | "bottom" | "left" | "right""top"Preferred placement. Flips to the opposite side if it would overflow the viewport.
delaynumber400Milliseconds before the tooltip appears on hover/focus.
childrenReactElementThe trigger element. Must be a single React element — Tooltip clones it to attach event handlers.

Mobile

Tooltips depend on hover — a concept that doesn't exist on touch screens. On mobile, tooltips are invisible unless you provide an alternative trigger strategy.

9:41

Article

Long press for tooltip

On mobile: tap-and-hold or use visible labels instead of hover tooltips

Save for later
Share
Comment
More
ScenarioGuidance
No hover on touchTooltips do not appear on tap by default. Never put information in a tooltip that is required to understand or operate the control — it will be inaccessible to touch users.
Tap-to-show patternFor icon-only controls, show the tooltip on the first tap and perform the action on the second tap. Or show a bottom drawer with the action label on long-press.
Prefer visible labelsOn mobile, replace icon-only buttons that rely on tooltips with visible text labels, or show labels beneath icons. This removes the need for tooltip discovery entirely.
Long-press triggerAdd a touchstart timer (300ms) to trigger the tooltip on long-press. Dismiss it on touchend or after 2 seconds. This mirrors iOS UIMenu behavior.
Positioning on small screensTooltips positioned to the top can be clipped by the top of the viewport on short-screen devices. Prefer bottom or auto placement on mobile.

Accessibility

  • The tooltip panel has role="tooltip". Wire aria-describedby on the trigger pointing to the tooltip id for full ARIA compliance.
  • Tooltips appear on focus as well as hover — keyboard users get the same information as mouse users.
  • Tooltip text must be purely supplementary. Never put information in a tooltip that is required to operate the control.
  • Avoid rich interactive content inside tooltips — they are pointer-events-none and cannot be focused.
  • For icon-only buttons, prefer aria-label over a tooltip as the primary accessible name. The tooltip then provides a secondary affordance.