Skip to content
Component installs need the registry setup— the Base UI shadcn project, the @vegastack namespace and the Cloudflare Access service token.
VegaStack Design

Tooltip

A floating label that appears on hover or focus — smart shared delay, rich content, optional keyboard hints, and collision-aware positioning.

Status
stable
Since
0.1.0
Accessibility pattern
APG tooltip

Last updated

Install

Add Tooltip from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.

pnpm dlx shadcn@latest add @vegastack/tooltip

The same command installs the registry items it composes: @vegastack/kbd, @vegastack/floating-surface.

Usage

import {
  Tooltip,
  TooltipTrigger,
  TooltipContent,
} from "@/components/ui/tooltip";

<Tooltip>
  <TooltipTrigger render={<Button variant="outline">Hover me</Button>} />
  <TooltipContent>Add to your library</TooltipContent>
</Tooltip>;

Provider

Tooltip needs a single Tooltip.Provider ancestor that owns the shared open/close delay. In VegaStack apps it is already mounted inside VegaStackProvider, so you never render it yourself — just drop tooltips anywhere below the app root. Once one tooltip opens, neighbouring tooltips open instantly (skip delay).

For a standalone tree (Storybook, tests, an isolated widget) wrap the region in the re-exported TooltipProvider.

Anatomy

Tooltip is a compound component. Compose the flat parts inside the root:

Tooltip — data-slot="tooltip"
TooltipArrow
TooltipContent
TooltipKbd — data-slot="tooltip-kbd"
TooltipTrigger — data-slot="tooltip-trigger"
<Tooltip delay={400}>
  <TooltipTrigger render={<Button />} />
  <TooltipContent side="top" arrow>
    Label
    <TooltipKbd keys={["⌘", "K"]} />
  </TooltipContent>
</Tooltip>
  • Tooltip — the root (data-slot="tooltip"). Groups the trigger and content; renders no DOM of its own. Accepts delay to override the provider's shared delay.
  • TooltipTrigger — the anchored element (data-slot="tooltip-trigger"). Renders a <button>; pass render to project the tooltip onto your own element (Base UI render composition).
  • TooltipContent — the floating panel (data-slot="tooltip-content"). Bundles Base UI's Portal + Positioner + Popup into one part, exposes portalProps / positionerProps, and can wrap children in an optional Base UI Viewport via viewportProps. Themed with the inverted bg-foreground / text-background surface; animated via data-[starting-style] / data-[ending-style].
  • TooltipArrow — an optional triangle pointing at the trigger (data-slot="tooltip-arrow"). Rendered automatically when TooltipContent gets arrow, or compose it directly.
  • TooltipKbd — render a keyboard shortcut hint as styled <kbd> keys (data-slot="tooltip-kbd").

Examples

Positioning

Set side (top / right / bottom / left) and sideOffset on the content. sideOffset accepts Base UI Positioner offset values, including offset functions. The tooltip flips automatically to avoid viewport collisions.

Alignment

Use align (start / center / end) to shift the tooltip along the chosen side. The default is center.

Offset

sideOffset controls the gap between the trigger and the tooltip (default 8, the shared detached-panel offset). Pass a number for a fixed gap, or an offset function ({ side, align, anchor, positioner }) => number to resolve the distance per placement.

<TooltipContent side="top" sideOffset={({ side }) => (side === "top" ? 24 : 8)}>
  Resolved per side
</TooltipContent>

Delay

Set delay (in milliseconds) on the root to override the provider's shared open delay for a single tooltip — delay={0} opens instantly, a larger value makes it more deliberate.

Keyboard hint

Pair a label with a shortcut using TooltipKbd. Pass an array of key tokens (or a string, split per glyph).

Arrow

Set arrow on the content to render a directional pointer.

Playground

Pick a side and toggle the arrow, hover the trigger, then copy the generated JSX.

<Tooltip>
  <TooltipTrigger render={<Button variant="outline">Hover me</Button>} />
  <TooltipContent>Add to your library</TooltipContent>
</Tooltip>

API Reference

Tooltip

PropTypeDefaultDescription
childrenReact.ReactNodeThe trigger and content parts.
delaynumberHow long to wait before opening, in milliseconds. Forwarded to the trigger. Falls back to the provider's shared delay when omitted.

Data attributes and CSS variables on Tooltip

AttributeValues
data-slot"tooltip"

TooltipTrigger

TooltipTrigger adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).

Data attributes and CSS variables on TooltipTrigger

AttributeValues
data-slot"tooltip-trigger"

TooltipContent

PropTypeDefaultDescription
alignAlign'center'Alignment relative to the chosen side.
arrowbooleanfalseRender a directional arrow pointing at the trigger.
portalPropsOmit<Omit<TooltipPortalProps, "ref"> & React.RefAttributes<HTMLDivElement>, "children">Props forwarded to the underlying Base UI Portal.
positionerPropsOmit<Omit<TooltipPositionerProps, "ref"> & React.RefAttributes<HTMLDivElement>, "align" | "children" | "side" | "sideOffset">Props forwarded to the underlying Base UI Positioner.
sideSide'top'Which side of the trigger to place the tooltip on.
sideOffsetnumber | OffsetFunction8Distance between the trigger and the tooltip, in pixels.
viewportPropsOmit<Omit<TooltipViewportProps, "ref"> & React.RefAttributes<HTMLDivElement>, "children">Props forwarded to an optional Base UI Viewport that wraps tooltip children.

TooltipArrow

TooltipArrow adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).

TooltipKbd

PropTypeDefaultDescription
keys*string | readonly string[]The shortcut — a string (split per glyph) or explicit key tokens.
os"mac" | "other"'other'Platform label mode, forwarded to Kbd. Resolve it at the call site with usePlatform(); the default matches that hook's SSR fallback.

Data attributes and CSS variables on TooltipKbd

AttributeValues
data-slot"tooltip-kbd"

Accessibility

  • The popup carries role="tooltip", so assistive technology announces it as a tooltip and associates it with the trigger.
  • Tooltips open on hover and keyboard focus — never hover-only — so keyboard and screen-reader users get the same hint.
  • Hover/focus-only by design — no touch trigger. This is a Base UI upstream constraint, not a bug: Tooltip opens on pointerenter/focus and has no touch-tap handling, so on phones/tablets (any device that can't hover) content that lives only inside a tooltip is permanently unreachable. Never make tooltip content essential-only — the trigger's own label/icon must stand on its own. When the tooltip exists specifically to reveal overflow/clipped text, reach for TruncatedText instead of a bare Tooltip — it detects (hover: none) and swaps the hover-only tooltip for a tap-to-toggle disclosure on touch, while keeping the Tooltip (and its focus trigger) unchanged for keyboard/mouse users. That component is the reference pattern for touch disclosure of clipped content.
  • A tooltip is a supplement, not a replacement for an accessible name. Give icon-only triggers their own aria-label; don't rely on the tooltip alone.
  • Content is non-interactive by design (it dismisses on blur/escape). Put actions in a Popover, not a Tooltip.
KeyAction
TabMove focus to the trigger — the tooltip opens.
EscDismiss the open tooltip.
ContractStates tested
Behaviourdefault, open
Accessibilitysemantic-html
Visualdefault, hover

Do / Don't

Do
Use tooltips for short, supplementary labels on icon buttons and toolbar actions.
Don't
Put links, buttons, or essential content inside a tooltip — it isn't focusable or reliably reachable. Use a Popover.

On this page