Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Components

Free

Tooltip for React

Add a hover label to an icon button, and show the same line when focus lands on it. For React with Base UI, when the action isn't already written on the screen.

Framework
UI library

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.

Preview width

Installation

Sign in to get your install command
Free with an account. It carries your token, so the CLI installs everything in one go.

Anatomy

Tooltip anatomy: content, positioner, arrow, trigger
TooltipTrigger
TooltipArrow
└── TooltipArrowTip
TooltipContent
└── TooltipPositioner

Usage

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.
Escape
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.

Preview width

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.

Preview width

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.

Preview width

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.

Preview width

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.

How do I install the Tooltip in React?

Run the command in the Installation section above: it adds the Tooltip to your ui folder. The command uses the shadcn CLI.

API Reference

Tooltip

data-slot="tooltip"

PropType
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 – boolean

eventDetails – 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"

PropType
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"

PropType
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"

PropType
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
CSS variableType
--transform-originstring

Where the content scales from, on its trigger's side.

TooltipContent

<div> data-slot="tooltip-content"

PropType
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.

CSS variableType
--transform-originstring

Where the content scales from, on its trigger's side.

TooltipProvider

data-slot="tooltip-provider"

PropType
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

    Buttons

    Icon-only toolbar labels

  • Free

    Kbd

    Typography

    Shortcut in the hint

  • Free

    Toggle

    Selection

    Why this is pressed

  • Free

    Sidebar

    Navigation

    Collapsed nav labels

  • Free

    Trailing help icon

  • Free

    Badge

    Feedback

    Status needs a sentence