Tooltip
A short hint on hover or keyboard focus that opens after a brief rest, switches instantly between neighbours and shows shortcuts.
pnpm dlx shadcn@latest add https://hextaui.com/r/tooltip.jsonAdds the component, the HextaUI theme tokens and any HextaUI components it depends on.
Add the theme tokens to your global CSS, if you haven’t yet.
Install the dependencies.
pnpm add @base-ui/react cnCopy and paste the following code into your project.
components/ui/tooltip.tsx Update the import paths to match your project setup.
A tooltip opens after a short rest on the trigger, or right away when the trigger gets keyboard focus. Once one is showing, its neighbours open instantly and without animation, so scanning a toolbar feels like reading labels rather than waiting for each one.
That instant switching works between tooltips that share a TooltipProvider. Wrap your app (or a toolbar) in one to share the delay. A tooltip without a provider still works on its own with the same defaults.
Sides
Set side on TooltipContent. Logical sides follow the reading direction, and the tooltip flips when there isn’t room.
With an arrow
arrow adds a pointer that stays on the trigger even when the tooltip shifts to fit the screen.
Keyboard shortcut
Put a Kbd or KbdGroup after the label. It picks up the tooltip’s colors and sits at the end, showing ⌘ on Apple devices and Ctrl elsewhere.
Toolbar
Rest on one button, then slide along the group. Tooltips under one TooltipProvider swap instantly while you move between them.
Disabled
A disabled button can still explain why with focusableWhenDisabled, which keeps it hoverable and in the tab order. disabled on Tooltip turns the tooltip off.
Long content
Text wraps at a comfortable width and never runs past the screen edge, even long URLs.
Controlled
Pass open and onOpenChange to drive the tooltip yourself. The reason tells you what opened or closed it.
Open: false · last reason: none
One tooltip, many triggers
Create a handle with createTooltipHandle and pass a payload from each trigger to share a single tooltip.
Right to left
Inside dir="rtl", inline-end opens on the left and the shortcut moves to the other end.
| Key | Action |
|---|---|
| Tab | Focusing the trigger from the keyboard opens its tooltip right away. |
| Esc | Closes the tooltip and keeps focus on the trigger. |
| EnterSpace | Activates the trigger and closes the tooltip, so it doesn’t cover what happens next. |
- A tooltip is a visual hint, not a label. Give icon-only triggers an
aria-labelthat matches the tooltip text. - Nothing opens on touch, and a tap only does what the trigger does. Don’t put anything in a tooltip that people need; use a popover for that.
- Pointer users can move onto the tooltip to read or select it without it closing.
- With reduced motion on, the tooltip fades without scaling.
Built on the Base UI tooltip. Every part accepts the props of the primitive it wraps.
| Prop | Type | Default |
|---|---|---|
delayMilliseconds to rest on a trigger before opening. | number | 300 |
closeDelayMilliseconds before closing after leaving. | number | 0 |
timeoutHow long after one closes that the next opens instantly. | number | 400 |
| Prop | Type | Default |
|---|---|---|
open | boolean | – |
defaultOpen | boolean | false |
onOpenChange | (open, details) => void | – |
onOpenChangeCompleteCalled after the open or close animation. | (open) => void | – |
disabledTurns the tooltip off. | boolean | false |
disableHoverablePopupClose as soon as the pointer leaves the trigger. | boolean | false |
trackCursorAxis | "none" | "x" | "y" | "both" | "none" |
handleFrom createTooltipHandle. | TooltipHandle<Payload> | – |
children | ReactNode | ({ payload }) => ReactNode | – |
| Prop | Type | Default |
|---|---|---|
delayOverrides the provider delay for this trigger. | number | – |
closeDelay | number | – |
closeOnClick | boolean | true |
disabledStops this trigger opening the tooltip. The element stays enabled. | boolean | false |
handle | TooltipHandle<Payload> | – |
payload | Payload | – |
render | ReactElement | (props, state) => ReactElement | <button> |
| Attribute | Description |
|---|---|
data-slot="tooltip-trigger" | The trigger. |
data-popup-open | Present while its tooltip is open. |
data-trigger-disabled | Present when the trigger can’t open the tooltip. |
| Prop | Type | Default |
|---|---|---|
side | "top" | "bottom" | "left" | "right" | "inline-start" | "inline-end" | "top" |
align | "start" | "center" | "end" | "center" |
sideOffset | number | (data) => number | 6, or 8 with arrow |
alignOffset | number | 0 |
arrow | boolean | false |
collisionPadding | number | 8 |
arrowPadding | number | 8 |
portalPropsSuch as container or keepMounted. | TooltipPortal props | – |
| Attribute | Description |
|---|---|
data-slot="tooltip-content" | The popup. |
data-slot="tooltip-positioner" | The positioning wrapper around the popup. |
data-side | The side it opened on after flipping. |
data-align | The alignment. |
data-instant | "delay", "focus" or "dismiss" when it opens or closes without animation. |
data-starting-style | Present while it animates in. |
data-ending-style | Present while it animates out. |
--available-width | Room between the trigger and the screen edge. |
--transform-origin | The point the tooltip scales from. |
Rendered for you by arrow. Use it directly only when composing your own content part.
| Attribute | Description |
|---|---|
data-slot="tooltip-arrow" | The arrow. |
data-uncentered | Present when it can’t point at the trigger’s center. |