DocumentationComponentsTheme Creator
Theme CreatorGitHubIntroduction
InstallationUsageThemingMotion
Components
AlertBannerToastEmptyStateTooltipCoachMark
AI agentsContributing
  1. Components
  2. Tooltip

Tooltip

Short hover and keyboard-focus hint anchored to a control. Composable trigger and content on Base UI, role=tooltip, 4 sides × 3 alignments with collision flipping, 5 intent plates, 3 sizes, an optional arrow, grouped delays via a provider, and a label/description ARIA switch.

$ pnpm add @tessinaui/ui

A tooltip never appears on touch. Base UI opens it on hover with mouseOnly, and on focus only under keyboard modality — so a phone or tablet user sees nothing, and there is no long-press fallback. Anything a touch user must be able to read cannot live in a tooltip: put it in visible text, in the control's aria-label, or behind a Popover, which opens on tap.

When to use

  • Naming an icon-only control. Set relationship="label" so the tip is the control's accessible name, not a second description on top of it.
  • A short supplement to a visible label: a keyboard shortcut, a one-line clarification, the full text of a label that was truncated.
  • Hint text a user can miss. A tooltip is hidden until hover or keyboard focus, so everything in it must be optional.
  • Dense toolbars. Wrap the group in TooltipProvider so the second and later tips open instantly once the first has been shown.

When not to use

  • For anything a user needs to finish a task. It is absent on touch and easy to miss. Put it in visible text, helper text or a label.
  • For interactive content. Links and buttons inside a tip cannot be reached. Use Popover.
  • For a rich preview of a person, page or record. Use HoverCard.
  • To repeat a visible label. If the control already says it, the tip adds noise.
  • On a non-focusable element or a disabled control. Keyboard users never reach it. See Disabled triggers for the workaround.
  • For long text. Past a short phrase or sentence, use a Popover or put the text on the page.

Installation

Terminal
pnpm add @tessinaui/ui

Usage

example.tsx
import { Tooltip, TooltipTrigger, TooltipContent } from "@tessinaui/ui";
example.tsx
<Tooltip>
  <TooltipTrigger render={<Button variant="outline">Save</Button>} />
  <TooltipContent>Saves your draft without publishing it</TooltipContent>
</Tooltip>

TooltipTrigger renders a <button> by default; render swaps in any element and keeps the wiring. TooltipContent collapses Base UI's portal, positioner and popup into one part, and draws the arrow for you unless you pass arrow={false}.

Rules

These are house rules because the design systems we surveyed agree on them, not because of a local preference.

  • Nothing interactive inside. No links, no buttons, nothing focusable — a tooltip closes as the pointer leaves the trigger, so anything in it is a trap. Use Popover for content people need to reach.
  • Nothing essential inside. A tooltip is hidden by default, absent on touch, and easy to miss. If a task cannot be completed without the text, it belongs in helper text or a label.
  • The trigger must be focusable, so the tip opens for a keyboard user too.
  • Keep it short — a phrase or a sentence, sentence case.
  • Don't put a tooltip on a disabled control if you can avoid it. See Disabled triggers for the case where you cannot.

Which component?

You wantUse
A short hint on hover/focusTooltip
Content people tap, click or interact withPopover
A rich preview of an entity on hoverHoverCard
A tour step, coach mark or feature calloutCoachMark
A value readout on a chartChart's own ChartTooltip

Examples

Default

Sides

side picks the edge; the tip flips automatically when it would collide with the viewport. (Flipping is switched off in this example so all four stay put.)

Alignment

align slides the tip along the chosen side.

Intents

Five plates — none is the always-dark overlay, and the four semantic values paint the matching surface. The arrow reads the plate's own published fill, so it can never drift off the tip.

Delay

delay and closeDelay are separate, and both are inherited by the trigger.

Grouped delays

Wrap a cluster in TooltipProvider: the first tip waits out the delay, then every neighbour you sweep onto opens instantly for timeout ms. Without it, a toolbar makes you wait at every single icon.

Keyboard shortcuts

The most common piece of structure inside a real tooltip. Key renders on-color caps, so they read on the plate with no extra styling.

Naming vs describing an icon button

relationship decides what the tip is to assistive tech, and it is the one prop worth thinking about on every tooltip you write.

  • "description" (default) — the tip adds detail to a control that already has a name, via aria-describedby while the tip is open.
  • "label" — the tip becomes the trigger's accessible name, for an icon-only button. The text is also rendered into an always-present visually hidden copy, because a name that only exists while the pointer hovers is no name at all; the visible popup is then hidden from AT so nothing is announced twice.
  • "none" — no ARIA wiring, for a trigger that is already fully described.

label is for a bare trigger you built yourself. The house IconButton requires an aria-label or aria-labelledby in its type — it can never be the unnamed case — so pair a tooltip on one with description and let it add detail instead.

Disabled triggers

A disabled button fires no pointer events, so a tooltip attached to one never opens. Make the wrapper the trigger instead.

Prefer not to need this. In a survey of real products, nobody explained a disabled control with a hover tip — they used an inline banner or helper text, which a touch user and a screen-reader user can also reach.

On color

The tip is its own on-color plate wherever it opens, so content inside it, like these Key caps, needs no tone prop. On a coloured Surface the trigger inherits on-color as usual.

RTL

dir="rtl" on Tooltip reaches the portalled positioner, so align="start" hugs the trigger's right edge and end its left. side stays physical: left opens on the left in both directions. With dir unset the tip takes the direction around its trigger.

API Reference

Tooltip

The root. Holds the style context and the open state.

PropTypeDefaultDescription
size"sm" | "md" | "lg""md"Padding and text size
rounded"none" | "sm" | "md" | "lg" | "full""md"Corner radius. The arrow's tip radius cascades from it
intent"none" | "error" | "warning" | "success" | "info""none"Plate colour
relationship"description" | "label" | "none""description"How the tip relates to the trigger for AT
delaynumber600ms before opening on hover
closeDelaynumber0ms before closing
openboolean—Controlled open state
defaultOpenboolean—Uncontrolled initial state
onOpenChange(open, event) => void—Fires on every open/close
disabledbooleanfalseThe tip never opens. Does not disable the trigger element
hoverablePopupbooleantrueKeep the tip open while the pointer is over the tip itself
trackCursorAxis"none" | "x" | "y" | "both""none"Follow the cursor instead of anchoring to the trigger's box
dir"ltr" | "rtl"—Text direction applied to the popup. Unset, it is read around the trigger

TooltipTrigger

Renders a <button>, or any element via render. Accepts delay and closeDelay to override the root's, plus every native button prop.

TooltipContent

PropTypeDefaultDescription
side"top" | "right" | "bottom" | "left""top"Which side of the trigger to sit on
align"start" | "center" | "end""center"Alignment along that side
sideOffsetnumber6Distance from the trigger, floored to 12 when the arrow is on
alignOffsetnumber0Offset along the side axis
collisionPaddingnumber8Keep this far from the viewport edge
collisionAvoidanceobject—Base UI's config. { side: "none" } pins the tip to its declared side
arrowbooleantrueRender the pointer
portalbooleantrueRender in a portal
containerHTMLElement | nulldocument.bodyPortal target
positionerClassNamestring—Classes for the positioner element

size, rounded and intent can also be set here to override the root.

TooltipProvider

PropTypeDefaultDescription
delaynumber600Shared open delay for the group
closeDelaynumber0Shared close delay
timeoutnumber400Window after a tip closes during which the next opens instantly

TooltipArrow

Rendered for you by TooltipContent. Pass one explicitly to add a className; yours wins over the automatic one.

It is the house speech-bubble tail — the same contour Popover, HoverCard, DropdownMenu, CoachMark and NavigationMenu point with, drawn at the smaller of its two scales because a tip is text rather than a panel. Its apex radius cascades from rounded, and it paints itself from the plate's own published fill, so every intent has exactly one colour source. It carries no outline: the tip has no border for one to continue.

Accessibility

  • The popup is role="tooltip" with an id, and the trigger points aria-describedby at it only while it is open — a permanent reference to an unmounted popup is a dangling IDREF that axe flags.
  • A caller's own aria-describedby / aria-labelledby is kept, and ours appended, never clobbered.
  • Escape closes the tip.
  • The tip opens on keyboard focus, not just hover.
  • VoiceOver passed 2026-09-14 (Safari). A description tip reads the name, then the tip: "Hover me button", "Saves your draft without publishing it". On an icon button: "Delete Deletes the item for everyone on the team button". With relationship="label" the tip replaces the name: "Edit item details button". Chrome was not re-run. NVDA and JAWS are untested — no Windows machine in the loop.
EmptyStateCoachMark

On this page

When to useWhen not to useInstallationUsageRulesWhich component?ExamplesDefaultSidesAlignmentIntentsDelayGrouped delaysKeyboard shortcutsNaming vs describing an icon buttonDisabled triggersOn colorRTLAPI ReferenceTooltipTooltipTriggerTooltipContentTooltipProviderTooltipArrowAccessibility