Skip to main content

Tooltip

A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.

When to use

A Tooltip shows a short, non-interactive label when a control receives keyboard focus or a pointer hover. Reach for it to clarify the purpose of an icon-only button, surface a keyboard shortcut, or show a brief hint about a control. Do NOT put buttons, links, or form controls inside a Tooltip.

  • Prefer Popover when the floating content needs buttons, links, inputs, or anything else focusable.
  • Prefer HoverCard for a sighted-only preview of content that already lives behind a link.

Mount TooltipProvider once at the app root when you want shared tooltip behavior, such as consistent delay and hover settings.

warning

Per the WAI-ARIA tooltip pattern, tooltips must not contain interactive or focusable content: the tooltip itself never receives focus, so anything inside it is unreachable for keyboard users. If the floating content needs to be interactive, use Popover instead.
import { Button } from "@ngrok/mantle/button";import { Tooltip } from "@ngrok/mantle/tooltip"; <Tooltip.Root>	<Tooltip.Trigger asChild>		<Button type="button" appearance="filled" intent="neutral">			Hover me		</Button>	</Tooltip.Trigger>	<Tooltip.Content>		<p>This feature is part of your plan</p>	</Tooltip.Content></Tooltip.Root>;

Layering and z-index

Tooltip.Content renders at Tailwind z-50, Mantle's float tier. When composed inside an open Dialog, AlertDialog, or Sheet, it portals into that overlay's positioner and paints above the overlay that owns it. Outside every overlay, it portals to document.body, below the overlay tier (z-60). When sibling floats share a container, the most recently mounted float paints on top.

Tooltip does not render a backdrop. Modal layers such as Dialog, Sheet, and AlertDialog do render backdrops, so stacking those layers compounds the dim and blur effect on the layers underneath.

Composition

Compose the parts of a Tooltip together to build your own:

Tooltip.Root├── Tooltip.Trigger└── Tooltip.Content

Polymorphism

Tooltip.Trigger accepts asChild, which renders your single child in place of the default trigger element while keeping the tooltip's hover, focus, and dismissal behavior.

Tooltip.Trigger asChild is the standard way to use this component: wrap the element that should show the tooltip (a Button, an IconButton, a link, or any focusable control) so the trigger behavior attaches to it instead of nesting it inside an extra element. Remember that the trigger must be focusable for the tooltip to be reachable by keyboard.

Tooltip.Root is a state provider and renders no DOM, so it takes no asChild.

warning

Tooltip.Content does not take asChild. The content renders its own arrow alongside your children, so a slot would receive more than the single element it requires. Style Tooltip.Content with className instead.

API Reference

note

Wrap your app with TooltipProvider once at the root to share global tooltip behavior — such as consistent delay and hover settings — across your application.

TooltipProvider

Wraps your app to set shared global behavior for your tooltips, such as consistent delay and hover settings. Mount exactly one instance, preferably at the root of your app. A nested provider applies its own timing props to its subtree. It also tracks skipDelayDuration separately, so a pointer that moves from one provider's trigger to the other's waits the delay again.

PropTypeDefaultDescription
delayDuration?number0The duration from when the mouse enters a tooltip trigger until the tooltip opens, in milliseconds.
skipDelayDuration?number300How much time a user has to enter another trigger without incurring a delay again, in milliseconds.
disableHoverableContent?booleanfalsePrevents Tooltip.Content from remaining open when hovering. Disabling this has accessibility consequences.

Tooltip.Root

The root component that manages the open/closed state of the tooltip. Wrap your app in TooltipProvider when you want shared app-wide delay and hover settings.

Tooltip.Content renders at Tailwind z-50, Mantle's float tier. When composed inside an open Dialog, AlertDialog, or Sheet, it portals into that overlay's positioner and paints above the overlay that owns it. Outside every overlay, it portals to document.body, below the overlay tier (z-60). When sibling floats share a container, the most recently mounted float paints on top.

PropTypeDefaultDescription
defaultOpen?booleanfalseThe open state of the tooltip when it is initially rendered. Use when you do not need to control its open state.
open?booleanThe controlled open state of the tooltip. Must be used in conjunction with onOpenChange.
onOpenChange?(open: boolean) => voidEvent handler called when the open state of the tooltip changes.
delayDuration?numberOverride the duration given to the TooltipProvider to customize the open delay for a specific tooltip.
disableHoverableContent?booleanOverride the disableHoverableContent given to the TooltipProvider to customize the behavior for a specific tooltip.

Tooltip.Trigger

The button that toggles the tooltip. By default, the Tooltip.Content will position itself against the trigger.

PropTypeDefaultDescription
asChild?booleanfalseUse the asChild prop to compose the tooltip trigger onto an alternative element.

Tooltip.Content

The component that pops out when the tooltip is open.

Tooltip.Content renders at Tailwind z-50, Mantle's float tier. When composed inside an open Dialog, AlertDialog, or Sheet, it portals into that overlay's positioner and paints above the overlay that owns it. Outside every overlay, it portals to document.body, below the overlay tier (z-60). When sibling floats share a container, the most recently mounted float paints on top.

children render inside a <div data-slot="tooltip-label">. The arrow is a permanent element sibling, so without the wrapper a bare text body is never a lone child: a browser translation engine reparents that text node, and the removal React runs when the body changes shape or goes away throws. The wrapper is a <div> because a tooltip body is often a <p>, which a <span> may not contain. It is display: contents, so it adds no box and every child of your body stays a layout child of the surface. Browser translation covers the mechanism and the rules your own components need.

PropTypeDefaultDescription
side?"top" | "right" | "bottom" | "left""top"The preferred side of the trigger to render against when open. Will be reversed when collisions occur and avoidCollisions is enabled.
sideOffset?number4The distance in pixels from the trigger.
align?"start" | "center" | "end""center"The preferred alignment against the trigger. May change when collisions occur.
alignOffset?number0An offset in pixels from the start or end alignment options.
avoidCollisions?booleantrueWhen true, overrides the side and align preferences to prevent collisions with boundary edges.
data-slot?string—A composing part's data-slot chain, joined ahead of this part's own tooltip-content rather than replacing it.

Data attributes

Data AttributeValueDescription
data-slot"tooltip-content"On the tooltip surface, with any composing part's chain ahead of it.
data-slot"tooltip-label"On the <div> wrapping children.
data-state"delayed-open" | "instant-open" | "closed"The open state Radix stamps on the surface.
data-side"top" | "right" | "bottom" | "left"Which side of the trigger the surface resolved to.