shadcn.io is not affiliated with official shadcn/ui
One short line shows on hover or focus, then it goes away. Don't put a link in it.
Use it when the control doesn't already say what it does, like an icon button. If the pointer should enter the content, use a Hover Card. In React, Base UI opens it on hover and keyboard focus, and a phone user can miss it.
Installation
Anatomy

TooltipTrigger
TooltipArrow
└── TooltipArrowTip
TooltipContent
└── TooltipPositionerUsage
import { Button } from "@/components/ui/button";
import {
Tooltip, TooltipContent, TooltipProvider, TooltipTrigger,
} from "@/components/ui/tooltip";const TooltipDemo = () => (
<TooltipProvider>
<Tooltip>
<TooltipTrigger render={<Button variant="outline" />}>
Hover
</TooltipTrigger>
<TooltipContent>
<p>Add to library</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);Accessibility
- Tab
- Opens/closes the tooltip without delay.
- EscEscape
- If open, closes the tooltip without delay.
- Enter
- Activate the focused item.
- Space
- Activate the focused control.
Demos
Sides
The same label, Add to library, opens on the left, the top, the bottom, and the right. The default is the top, and it should flip when there is no room, but you pin a side when the default would cover the thing the person is editing. Pick the side with empty space, not the side that looks balanced on an empty page.
Keyboard
The button is only a save icon. Its accessible name is Save Changes, and the tooltip repeats that name next to the S shortcut. Use this when an icon button has a keyboard equivalent worth advertising. The shortcut shown here is a hint, not the binding, so you still register the key yourself.
Disabled
A disabled button receives no hover or focus, so a tooltip wrapped around it never opens. Here the trigger is a span around the button, and that span takes the pointer. The label says the feature is unavailable. Wrap like this when people need the reason, and otherwise leave the button focusable and ignore the click.
Rtl
The first of two rows is the physical sides, labeled in Arabic. The second is labeled as the start and the end of the line, while the placement values are still left and right, so you can see those edges under a right-to-left page. The tooltip text is Arabic too. Set direction on the container when the page itself is not already right to left.
FAQ
How do I change the Tooltip delay in React?
Set the delay where the base reads it; a provider sets it once for every tooltip inside it.
Use delay and closeDelay on TooltipTrigger, or delay on a TooltipProvider around a group.
Do I need a TooltipProvider with Base UI?
No, it is optional: wrap a group of tooltips in a TooltipProvider to share one delay between them, as the default demo does.
How do I change which side the Tooltip appears on?
It opens on top by default and flips when there is not enough room; the Sides demo shows all four sides.
Set side on TooltipContent, for example bottom.
Why doesn't the Tooltip show on a disabled button?
A disabled button gets no pointer or focus events, so it cannot open a Tooltip. Wrap the button in a span, as the Disabled demo does, so the span receives the hover; or keep the button enabled with aria-disabled and ignore its clicks.
Does the Tooltip work on mobile and touch screens?
Not as a hover hint: touch screens have no hover, so a Tooltip mostly shows when its trigger gets focus. For information people must be able to read on a phone, use a Toggle Tooltip, which opens on tap.
Is the Tooltip accessible to keyboard and screen reader users?
Yes. It opens when its trigger gets keyboard focus and closes on Escape, as the Keyboard demo shows. Keep tooltip text short and never put links or buttons inside it.
Base UI does not link the text to the trigger, so give the trigger an accessible name of its own, such as an aria-label.
API Reference
Tooltip
data-slot="tooltip"
open?boolean
Whether the tooltip is currently open.
defaultOpen?boolean
Whether the tooltip is initially open.
To render a controlled tooltip, use the open prop instead.
- Default
false
onOpenChange?(open: boolean, eventDetails: TooltipRoot.ChangeEventDetails) → void
Event handler called when the tooltip is opened or closed.
- Type
(open: boolean, eventDetails: TooltipRoot.ChangeEventDetails) → void- Parameters
open– booleaneventDetails– TooltipRoot.ChangeEventDetails- Returns
void
actionsRef?React.RefObject<TooltipRoot.Actions | null>
A ref to imperative actions.
- unmount: Ends the closing phase of the tooltip after an externally controlled closing animation finishes.
Call preventUnmountOnClose() in onOpenChange first, otherwise the tooltip completes closing on its own.
Whether it leaves the DOM is decided by keepMounted on the portal.
- close: Closes the tooltip imperatively when called.
- Type
React.RefObject<TooltipRoot.Actions | null>
children?React.ReactNode | PayloadChildRenderFunction<Payload>
The content of the tooltip.
This can be a regular React node or a render function that receives the payload of the active trigger.
- Type
React.ReactNode | PayloadChildRenderFunction<Payload>
defaultTriggerId?string | null
ID of the trigger that the tooltip is associated with.
This is useful in conjunction with the defaultOpen prop to create an initially open tooltip.
- Type
string | null
disabled?boolean
Whether the tooltip is disabled.
- Default
false
disableHoverablePopup?boolean
Whether the tooltip contents can be hovered without closing the tooltip.
- Default
false
handle?TooltipHandle<Payload>
A handle to associate the tooltip with a trigger. If specified, allows external triggers to control the tooltip's open state. Can be created with the Tooltip.createHandle() method.
- Type
TooltipHandle<Payload>
onOpenChangeComplete?(open: boolean) → void
Event handler called after any animations complete when the tooltip is opened or closed.
- Parameters
open– boolean- Returns
void
trackCursorAxis?"none" | "x" | "y" | "both"
Determines which axis the tooltip should track the cursor on.
- Type
"none" | "x" | "y" | "both"- Default
"none"
triggerId?string | null
ID of the trigger that the tooltip is associated with.
This is useful in conjunction with the open prop to create a controlled tooltip.
There's no need to specify this prop when the tooltip is uncontrolled (that is, when the open prop is not set).
- Type
string | null
render?ReactElement | ((props: object, state: object) => ReactElement)
Replace the host element or compose it with another component.
- Type
ReactElement | ((props: object, state: object) => ReactElement)
TooltipTrigger
<button> data-slot="tooltip-trigger"
closeDelay?number
How long to wait before closing the tooltip. Specified in milliseconds.
- Default
0
closeOnClick?boolean
Whether the tooltip should close when this trigger is clicked.
- Default
true
delay?number
How long to wait before opening the tooltip on hover. Specified in milliseconds.
- Default
600
disabled?boolean
If true, the tooltip will not open when interacting with this trigger.
Note that this doesn't apply the disabled attribute to the trigger element.
If you want to disable the trigger element itself, you can pass the disabled prop to the trigger element via the render prop.
- Default
false
handle?TooltipHandle<Payload>
A handle to associate the trigger with a tooltip.
- Type
TooltipHandle<Payload>
payload?Payload
A payload to pass to the tooltip when it is opened.
render?ReactElement | ((props: object, state: object) => ReactElement)
Replace the host element or compose it with another component.
- Type
ReactElement | ((props: object, state: object) => ReactElement)
TooltipArrow
<div> data-slot="tooltip-arrow"
render?ReactElement | ((props: object, state: object) => ReactElement)
Replace the host element or compose it with another component.
- Type
ReactElement | ((props: object, state: object) => ReactElement)
className?string
Merged with the part's classes.
TooltipPositioner
<div> data-slot="tooltip-positioner"
align?"start" | "center" | "end"
How to align the popup relative to the specified side.
- Type
"start" | "center" | "end"- Default
"center"
alignOffset?number | OffsetFunction
Additional offset along the alignment axis in pixels.
Also accepts a function that returns the offset to read the dimensions of the anchor
and positioner elements, along with its side and alignment.
The function takes a data object parameter with the following properties:
- data.anchor: the dimensions of the anchor element with properties width and height.
- data.positioner: the dimensions of the positioner element with properties width and height.
- data.side: which side of the anchor element the positioner is aligned against.
- data.align: how the positioner is aligned relative to the specified side.
``jsx
<Positioner
alignOffset={({ side, align, anchor, positioner }) => {
return side === top || side === bottom
? anchor.width
: anchor.height;
}}
/>
``
- Type
number | OffsetFunction- Default
0
anchor?Element | null | VirtualElement | React.RefObject<Element | null> | (() → Element | VirtualElement | null)
An element to position the popup against. By default, the popup will be positioned against the trigger.
- Type
Element | null | VirtualElement | React.RefObject<Element | null> | (() → Element | VirtualElement | null)
arrowPadding?number
Minimum distance to maintain between the arrow and the edges of the popup. Use it to prevent the arrow element from hanging out of the rounded corners of a popup.
- Default
5
collisionAvoidance?SideFlipMode | SideShiftMode
Determines how to handle collisions when positioning the popup.
side controls overflow on the preferred placement axis (top/bottom or left/right):
- flip: keep the requested side when it fits; otherwise try the opposite side
(top and bottom, or left and right).
- shift: never change side; keep the requested side and move the popup within
the clipping boundary so it stays visible.
- none: do not correct side-axis overflow.
align controls overflow on the alignment axis (start/center/end):
- flip: keep side, but swap start and end when the requested alignment overflows.
- shift: keep side and requested alignment, then nudge the popup along the
alignment axis to fit.
- none: do not correct alignment-axis overflow.
fallbackAxisSide controls fallback behavior on the perpendicular axis when the
preferred axis cannot fit:
- start: allow perpendicular fallback and try the logical start side first
(top before bottom, or left before right in LTR).
- end: allow perpendicular fallback and try the logical end side first
(bottom before top, or right before left in LTR).
- none: do not fallback to the perpendicular axis.
When side is shift, explicitly setting align only supports shift or none.
If align is omitted, it defaults to flip.
``jsx
<Positioner
collisionAvoidance={{
side: shift,
align: shift,
fallbackAxisSide: none,
}}
/>
``
- Type
SideFlipMode | SideShiftMode
collisionBoundary?"clipping-ancestors" | Element | Element[] | Rect
An element or a rectangle that delimits the area that the popup is confined to.
- Type
"clipping-ancestors" | Element | Element[] | Rect- Default
"clipping-ancestors"
collisionPadding?Padding
Additional space to maintain from the edge of the collision boundary.
- Default
5
disableAnchorTracking?boolean
Whether to disable the popup from tracking any layout shift of its positioning anchor.
- Default
false
positionMethod?"absolute" | "fixed"
Determines which CSS position property to use.
- Type
"absolute" | "fixed"- Default
"absolute"
side?"top" | "bottom" | "left" | "right" | "inline-end" | "inline-start"
Which side of the anchor element to align the popup against. May automatically change to avoid collisions.
- Type
"top" | "bottom" | "left" | "right" | "inline-end" | "inline-start"- Default
"top"
sideOffset?number | OffsetFunction
Distance between the anchor and the popup in pixels.
Also accepts a function that returns the distance to read the dimensions of the anchor
and positioner elements, along with its side and alignment.
The function takes a data object parameter with the following properties:
- data.anchor: the dimensions of the anchor element with properties width and height.
- data.positioner: the dimensions of the positioner element with properties width and height.
- data.side: which side of the anchor element the positioner is aligned against.
- data.align: how the positioner is aligned relative to the specified side.
``jsx
<Positioner
sideOffset={({ side, align, anchor, positioner }) => {
return side === top || side === bottom
? anchor.height
: anchor.width;
}}
/>
``
- Type
number | OffsetFunction- Default
0
sticky?boolean
Whether to maintain the popup in the viewport after the anchor element was scrolled out of view.
- Default
false
--transform-originstring
Where the content scales from, on its trigger's side.
TooltipContent
<div> data-slot="tooltip-content"
render?ReactElement | ((props: object, state: object) => ReactElement)
Replace the host element or compose it with another component.
- Type
ReactElement | ((props: object, state: object) => ReactElement)
className?string
Merged with the part's classes.
--transform-originstring
Where the content scales from, on its trigger's side.
TooltipProvider
data-slot="tooltip-provider"
closeDelay?number
How long to wait before closing a tooltip. Specified in milliseconds.
delay?number
How long to wait before opening the tooltip on hover. Specified in milliseconds.
- Default
0
timeout?number
Another tooltip will open instantly if the previous tooltip is closed within this timeout. Specified in milliseconds.
- Default
400
render?ReactElement | ((props: object, state: object) => ReactElement)
Replace the host element or compose it with another component.
- Type
ReactElement | ((props: object, state: object) => ReactElement)
With other components
- Free
Button
ButtonsIcon-only toolbar labels
- Free⌘+K
Kbd
TypographyShortcut in the hint
- FreeBi
Toggle
SelectionWhy this is pressed
- Free
Sidebar
NavigationCollapsed nav labels
- Free@
Input Group
InputsTrailing help icon
- Free
Badge
FeedbackStatus needs a sentence
Related
- Free
Hover Card
OverlaysRicher preview on hover
- Free
Popover
OverlaysClick to keep it open
- Free▼
Dropdown Menu
MenusActions, not a hint
- Free▼
Button Group
ButtonsCluster of icon actions
- Free
Avatar
MediaName on a face
- Free
Hint
FeedbackAnchored inside the parent