Skip to content
HyperDesign System

Tooltip

A short ink label that names a control on hover or keyboard focus. It describes; it never holds the only copy of something, since touch never opens it. Source: src/components/ui/tooltip.tsx.

Default

The common case: an icon-only button with an aria-label, and a tooltip that repeats it for pointer users. Tab onto a button to open it from the keyboard; Esc closes it.

With a shortcut

Put the key combination after the label in Kbd. Inside a tooltip, Kbd switches to a translucent cap that reads on ink.

Sides

Top by default. Radix flips it when there is no room.

Delay

Timing lives on TooltipProvider. The component's own default is 0ms; the app root wraps everything in a 300ms provider, so 300ms is what ships. Move along a row: once one tooltip has opened, its neighbours open at once.

0ms · component default
300ms · app root
700ms · Radix default

On a disabled control

A disabled button gets no hover or focus, so wrap it in a focusable span and put the trigger on that. Say why it is disabled.

Motion and layering

Known gap: the zoom and slide play on every open, including the instant re-opens while moving along a toolbar. The better pattern (delay first, then appear without animation while warm) is not built yet.

Tooltip motion and layering tokens
ValueTokenUsage
50Same layer as popovers. The brand ladder (--z-tooltip 300) is defined but not wired in yet.
140msFade, scale from 95% and an 8px slide, on every open.
90msFade and scale out.
quart-outThe entrance curve.
inkInk card; --foreground and --muted-foreground are remapped so nested text and Kbd read on it.
282pxWidth cap; longer text wraps. Corners rounded-lg (10px), sideOffset 6.

Props

Tooltip parts and props
Part / propTypeDefaultDescription
TooltipProviderRadix Tooltip.Provider—Shared timing for every tooltip inside it. The app root already renders one.
delayDurationnumber0Hover time before opening. The root provider passes 300.
skipDelayDurationnumber300Radix: after a tooltip closes, others open instantly for this long.
TooltipRadix Tooltip.Root—open, defaultOpen, onOpenChange, delayDuration (per tooltip).
TooltipTriggerRadix Tooltip.Trigger—Use asChild around a focusable element. Opens on hover and on keyboard focus.
TooltipContentRadix Tooltip.Content—Portals itself, pointer-events none. Takes side, align, collisionPadding. No arrow is rendered.
sideOffsetnumber6Gap from the trigger, in px.

Best practices

When to use

  • Every icon-only button gets one, repeating its aria-label.
  • Don't tooltip a button whose text already says the same thing; add a shortcut or say nothing.
  • Anything richer than a line (a link, an avatar, a preview) is a HoverCard or a Popover.

Behavior

  • Keep the root provider's 300ms. A nested delayDuration={0} is for dense toolbars only.
  • Tooltips never contain interactive content; the content is pointer-events none.
  • Touch devices never show them, so the information must exist elsewhere.

Accessibility

  • The tooltip text is exposed as the trigger's description; the aria-label is still its name.
  • It opens on keyboard focus and closes on Esc without moving focus.
  • A trigger must be focusable. Wrap disabled controls in a span with tabIndex={0}.

Content

  • A few words in sentence case, no full stop: “Copy message”, “Pin thread”.
  • Shortcuts go in Kbd after the label, one key per cap.