Skip to content

Tooltip

Minimal tooltip with hover delay and terminal styling. Built on the Popover primitive with full 4-side positioning and collision avoidance. Shows contextual information on hover without clipping by overflow ancestors.

Preview

Installation

$pnpm exec dgadd add ui/tooltip
[Installs to]src/components/ui/tooltip[Item]ui/tooltip

dgadd is not public on npm yet. Until the first release, pack @diffgazer/add from the repository and install that tarball into this app, which is what puts dgadd on pnpm exec.

UI components require Tailwind CSS v4. Local copy mode imports src/styles/styles.css; package mode uses @diffgazer/ui CSS once packages are available.

Usage

tsx
import { Tooltip } from "@/components/ui/tooltip";export default function TooltipBasicExample() {  return (    <div className="flex items-center gap-6">      <Tooltip content="Shorthand tooltip">        <button          type="button"          className="border border-foreground/30 px-3 py-1 font-mono text-sm focus-visible:outline-2 focus-visible:outline-ring focus-visible:outline-offset-0"        >          hover me        </button>      </Tooltip>      <Tooltip>        <Tooltip.Trigger>          <button            type="button"            className="border border-foreground/30 px-3 py-1 font-mono text-sm focus-visible:outline-2 focus-visible:outline-ring focus-visible:outline-offset-0"          >            compound          </button>        </Tooltip.Trigger>        <Tooltip.Content>          Custom tooltip with <strong>rich content</strong>        </Tooltip.Content>      </Tooltip>    </div>  );}

Examples

Open State (static)

Preview

Placement

Preview

Interactive Triggers

Preview

API Reference

Tooltip

NameTypeDefaultDescription
contentReactNodeShorthand: when set to renderable content, Tooltip renders children inside Tooltip.Trigger and content inside Tooltip.Content automatically. When omitted — or falsy, as in the conditional `content={isTruncated && label}` idiom — compose Tooltip.Trigger and Tooltip.Content explicitly via children.
openbooleanControlled open state.
defaultOpenbooleanfalseInitial open state for uncontrolled mode.
onOpenChange(open: boolean) => voidFired when the open state changes.
enabledbooleantrueDisables hover/focus triggering when false (use to suppress tooltips conditionally).
delayMsnumber500Show delay after the pointer enters the trigger; keyboard focus opens immediately.
closeDelayMsnumber150Hide delay after pointer/focus leaves the trigger or content.
childrenrequiredReactNodeTrigger element (shorthand mode) or full Tooltip.Trigger/Tooltip.Content composition.

Tooltip.Trigger

NameTypeDefaultDescription
childrenrequiredReactNode | (props: PopoverTriggerRenderProps) => ReactNodeTrigger element. Pass a single element, text, or a render function, with the same render-prop and clone semantics as Popover.Trigger.

Tooltip.Content

NameTypeDefaultDescription
side"top" | "bottom" | "left" | "right""top"Preferred side relative to the trigger.
align"start" | "center" | "end""center"Alignment along the chosen side.
sideOffsetnumber4Pixel gap from the trigger along the side axis.
alignOffsetnumber0Pixel offset along the alignment axis.
avoidCollisionsbooleantrueFlips to the opposite side, then cross-axis sides, then shifts within the viewport to keep the tooltip inside it.
collisionPaddingnumber8Minimum gap between the tooltip and the viewport edge.
childrenrequiredReactNodeTooltip body content.

Accessibility

Keyboard Navigation

Tooltip shows on focus and hides on blur, so Tab into the trigger reveals the tooltip. Escape dismisses the tooltip and returns focus to the trigger.

Interactive triggers with keyboard

Preview

Notes

Shorthand

Use the `content` prop on Tooltip root for simple text tooltips without compound nesting: `<Tooltip content="Help text"><button>…</button></Tooltip>`.

Positioning

Content supports all four sides via the `side` prop: `top` (default), `bottom`, `left`, `right`. Use `align` (`start`, `center`, `end`) for cross-axis alignment. Automatic flip and shift when content would overflow the viewport.

Portal Rendering

Content renders through the shared Portal primitive. When a PortalContainerProvider is present, Tooltip.Content uses that scoped container; otherwise it falls back to document.body. This keeps nested overlay trees in the same portal scope while still escaping overflow:hidden ancestors by default.

Conditional Display

Use the `enabled` prop to conditionally disable the tooltip without unmounting. Pairs well with useOverflowDetection — only show tooltip when content is actually truncated.

Delay

Default pointer show delay is 500ms (hover intent); keyboard focus opens immediately. The hide delay after pointer or focus leaves is 150ms. Customize them with `delayMs` and `closeDelayMs`.

Built on Popover

Tooltip is a thin wrapper around the Popover primitive with `triggerMode="hover"`. For interactive floating content, use Popover directly.

Surface

The surface (1px --border hairline, --surface-1 fill with a 1px --surface-1-highlight inner top lip, rounded-sm corners) comes from Popover.Content; Tooltip.Content only adds its max width, tight padding, and mono type. No drop shadow — the hairline plus the one-step surface lift carry the edge, which keeps the box in the same register as Menu, Select, and Dialog.

Reviewing the Open State

A hover tooltip shows nothing on a static page. Pass a controlled `open` with no setter to pin the surface open when you need the open state visible in documentation, a design review, or a visual-regression capture — see the Open State example. `defaultOpen` only seeds uncontrolled state, so hover leave or a scroll still closes it. Product code should use neither and let hover/focus drive the tooltip.

Controlled Mode

Supports `open`, `defaultOpen`, and `onOpenChange` props for programmatic control: `<Tooltip content="Help" open={showHelp} onOpenChange={setShowHelp}>...</Tooltip>`.

Source

Install via CLI: pnpm exec dgadd add ui/tooltip. Keyboard hooks are included as standalone copies. For the full experience, use --integration keys.

Highlighted source loads after this disclosure opens. Browse the source repository.