Tooltip
Tooltips are visual-only floating elements that display information about a trigger element when a user hovers or focuses it.
Introduction
Base UI components are all available as a single package.
npm install @base-ui-components/react
Once you have the package installed, import the component.
import { Tooltip } from '@base-ui-components/react/tooltip';
Anatomy
Tooltip is implemented using a collection of related components:
<Tooltip.Provider />
wraps around<App />
or a group of<Tooltip.Root>
s.<Tooltip.Root />
is a top-level component that wraps the other components.<Tooltip.Trigger />
renders the trigger element.<Tooltip.Positioner />
renders the tooltip's positioning element.<Tooltip.Popup />
renders the tooltip popup itself.<Tooltip.Arrow />
renders an optional pointing arrow, placed inside the popup.
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger />
<Tooltip.Positioner>
<Tooltip.Popup>
<Tooltip.Arrow />
</Tooltip.Popup>
</Tooltip.Positioner>
</Tooltip.Root>
</Tooltip.Provider>
Provider
Tooltip.Provider
provides a shared delay for tooltips so that once a tooltip is shown, the rest of the tooltips in the group don't wait for the delay before showing. You can wrap this globally, or around an individual group of tooltips anywhere in your React tree (or both).
<Tooltip.Provider>
<App />
</Tooltip.Provider>
Accessibility
Tooltips are only for sighted users with access to a pointer with hover capability or keyboard focus. This means you must supply an accessible name via aria-label
to trigger elements that don't contain text content, such as an icon button.
<Tooltip.Root>
<Tooltip.Trigger aria-label="Edit">
<EditIcon />
</Tooltip.Trigger>
<Tooltip.Positioner>
<Tooltip.Popup>Edit</Tooltip.Popup>
</Tooltip.Positioner>
</Tooltip.Root>
Your aria-label
and tooltip content should closely match or be identical so that screen reader users and sighted users receive the same information.
Tooltips should ideally also be secondary in nature, because touch users cannot see them. They are most useful as progressive enhancement in high-density desktop applications that have many icon buttons where visual labels are impractical to use.
Placement
By default, the tooltip is placed on the top side of its trigger, the default anchor. To change this, use the side
prop:
<Tooltip.Root>
<Tooltip.Trigger />
<Tooltip.Positioner side="right">
<Tooltip.Popup>Tooltip</Tooltip.Popup>
</Tooltip.Positioner>
</Tooltip.Root>
You can also change the align of the tooltip in relation to its anchor. By default, it is centered, but it can be aligned to an edge of the anchor using the align
prop:
<Tooltip.Positioner side="right" align="start">
<Tooltip.Popup>Tooltip</Tooltip.Popup>
</Tooltip.Positioner>
Due to collision detection, the tooltip may change its placement to avoid overflow. Therefore, your explicitly specified side
and align
props act as "ideal", or preferred, values.
To access the true rendered values, which may change as the result of a collision, the content element receives data attributes:
// Rendered HTML (simplified)
<div>
<div data-side="left" data-align="end">
Tooltip
</div>
</div>
This allows you to conditionally style the tooltip based on its rendered side or align.
Offset
The sideOffset
prop creates a gap between the anchor and tooltip popup, while alignOffset
slides the tooltip popup from its align, acting logically for start
and end
aligns.
<Tooltip.Positioner sideOffset={10} alignOffset={10}>
Delay
To change how long the tooltip waits until it opens or closes, use the delay
and closeDelay
props, which represent how long the tooltip waits after the cursor rests on the trigger to open, or moves away from the trigger to close, in milliseconds:
<Tooltip.Root delay={200} closeDelay={200}>
Controlled
To control the tooltip with external state, use the open
and onOpenChange
props:
function App() {
const [open, setOpen] = React.useState(false);
return (
<Tooltip.Root open={open} onOpenChange={setOpen}>
{/* Subcomponents */}
</Tooltip.Root>
);
}
Arrow
To add an arrow (caret or triangle) inside the tooltip content that points toward the center of the anchor element, use the Tooltip.Arrow
component:
<Tooltip.Positioner>
<Tooltip.Popup>
Tooltip
<Tooltip.Arrow />
</Tooltip.Popup>
</Tooltip.Positioner>
It automatically positions a wrapper element that can be styled or contain a custom SVG shape.
Cursor following
The tooltip can follow the cursor on both axes or one axis using the trackCursorAxis
prop on Tooltip.Root
. Possible values are: none
(default), both
, x
, or y
.
Anchoring
By default, the Trigger
acts as the anchor, but this can be changed to another element.
- A DOM element (stored in React state):
<Tooltip.Positioner anchor={anchorNode}>
- A React ref:
<Tooltip.Positioner anchor={anchorRef}>
- A virtual element object, consisting of a
getBoundingClientRect
method and an optionalcontextElement
property:
<Tooltip.Positioner
anchor={{
getBoundingClientRect: () => DOMRect,
// `contextElement` is an optional but recommended property when `getBoundingClientRect` is
// derived from a real element, to ensure collision detection and position updates work as
// expected in certain DOM trees.
contextElement: domNode,
}}
>
Styling
The Tooltip.Positioner
element receives the following CSS variables, which can be used by Tooltip.Popup
:
--anchor-width
: Specifies the width of the anchor element. You can use this to match the width of the tooltip with its anchor.--anchor-height
: Specifies the height of the anchor element. You can use this to match the height of the tooltip with its anchor.--available-width
: Specifies the available width of the popup element before it overflows the viewport.--available-height
: Specifies the available height of the popup element before it overflows the viewport.--transform-origin
: Specifies the origin of the popup element that represents the point of the anchor element's center. When animating scale, this allows it to correctly emanate from the center of the anchor.
By default, maxWidth
and maxHeight
are already specified on the positioner using --available-{width,height}
to prevent the tooltip from being too big to fit on the screen.
Animations
The tooltip can animate when opening or closing with either:
- CSS transitions
- CSS animations
- JavaScript animations
CSS transitions
Here is an example of how to apply a symmetric scale and fade transition with the default conditionally-rendered behavior:
<Tooltip.Popup className="TooltipPopup">Tooltip</Tooltip.Popup>
.TooltipPopup {
transform-origin: var(--transform-origin);
transition-property: opacity, transform;
transition-duration: 0.2s;
}
.TooltipPopup[data-starting-style],
.TooltipPopup[data-ending-style] {
opacity: 0;
transform: scale(0.9);
}
[data-starting-style]
and [data-ending-style]
are attributes that are added to the popup when it is first inserted to the DOM and when it is about to be removed, respectively. In these states, you can specify the initial and final styles of the popup, which for symmetric transitions should be the same.
In newer browsers, there is a feature called @starting-style
which allows transitions to occur on open for conditionally-mounted components:
/* Base UI API - Polyfill */
.TooltipPopup[data-starting-style] {
opacity: 0;
transform: scale(0.9);
}
/* Official Browser API */
@starting-style {
.TooltipPopup[data-open] {
opacity: 0;
transform: scale(0.9);
}
}
CSS animations
CSS animations can also be used, requiring only two separate declarations:
@keyframes scale-in {
from {
opacity: 0;
transform: scale(0.9);
}
}
@keyframes scale-out {
to {
opacity: 0;
transform: scale(0.9);
}
}
.TooltipPopup {
animation: scale-in 0.2s forwards;
}
.TooltipPopup[data-ending-style] {
animation: scale-out 0.2s forwards;
}
JavaScript animations
The keepMounted
prop lets an external library control the mounting, for example framer-motion
's AnimatePresence
component.
function App() {
const [open, setOpen] = useState(false);
return (
<Tooltip.Root open={open} onOpenChange={setOpen}>
<Tooltip.Trigger>Trigger</Tooltip.Trigger>
<AnimatePresence>
{open && (
<Tooltip.Positioner keepMounted>
<Tooltip.Popup
render={
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
/>
}
>
Tooltip
</Tooltip.Popup>
</Tooltip.Positioner>
)}
</AnimatePresence>
</Tooltip.Root>
);
}
Animation states
Four states are available as data attributes to animate the popup, which enables full control depending on whether the popup is being animated with CSS transitions or animations, JavaScript, or is using the keepMounted
prop.
[data-open]
-open
state istrue
.[data-closed]
-open
state isfalse
. Can still be mounted to the DOM if closing.[data-starting-style]
- the popup was just inserted to the DOM. The attribute is removed 1 animation frame later.[data-ending-style]
- the popup's final styles once it closes.
Instant animation
Animations can be removed under certain conditions using the data-instant
attribute on Tooltip.Popup
. This attribute can be used unconditionally, but it also has different values for granular checks:
data-instant="delay"
indicates the tooltip is grouped and instantly opened with no delay.data-instant="focus"
indicates it was triggered by keyboard focus.data-instant="dismiss"
indicates it was dismissed by pressing theesc
key.
In most of these cases, you'll want to remove any animations:
.TooltipPopup[data-instant] {
transition-duration: 0s;
}
Overriding default components
Use the render
prop to override the rendered elements with your own components.
// Element shorthand
<Tooltip.Popup render={<MyTooltipPopup />} />
// Function
<Tooltip.Popup render={(props) => <MyTooltipPopup {...props} />} />
API Reference
TooltipProvider
Provides a shared delay for tooltips so that once a tooltip is shown, the rest of the tooltips in the group will not wait for the delay before showing.
Prop | Type | Default | Description |
---|---|---|---|
closeDelay | number | The delay in milliseconds until tooltips within the group are closed. | |
delay | number | The delay in milliseconds until tooltips within the group are open. | |
timeout | number | 400 | The timeout in milliseconds until the grouping logic is no longer active after the last tooltip in the group has closed. |
TooltipRoot
The foundation for building custom-styled tooltips.
Prop | Type | Default | Description |
---|---|---|---|
animated | bool | true | Whether the tooltip can animate, adding animation-related attributes and allowing for exit animations to play. Useful to disable in tests to remove async behavior. |
closeDelay | number | 0 | The delay in milliseconds until the tooltip popup is closed. |
defaultOpen | bool | false | Whether the tooltip popup is open by default. Use when uncontrolled. |
delay | number | 600 | The delay in milliseconds until the tooltip popup is opened. |
hoverable | bool | true | Whether the user can move their cursor from the trigger element toward the tooltip popup element without it closing using a "safe polygon" technique. |
onOpenChange | func | Callback fired when the tooltip popup is requested to be opened or closed. Use when controlled. | |
open | bool | false | Whether the tooltip popup is open. Use when controlled. |
trackCursorAxis | enum | 'none' | Determines which axis the tooltip should track the cursor on. |
TooltipTrigger
Renders a trigger element that opens the tooltip.
Prop | Type | Default | Description |
---|---|---|---|
className | union | Class names applied to the element or a function that returns them based on the component's state. | |
render | union | A function to customize rendering of the component. |
TooltipPositioner
The tooltip positioner element.
Prop | Type | Default | Description |
---|---|---|---|
align | enum | 'center' | The align of the tooltip element to the anchor element along its cross axis. |
alignOffset | number | 0 | The offset of the tooltip element along its align axis. |
anchor | union | The element to which the tooltip element is anchored to. | |
arrowPadding | number | 5 | Determines the padding between the arrow and the tooltip edges. Useful when the tooltip element has rounded corners via border-radius . |
className | union | Class names applied to the element or a function that returns them based on the component's state. | |
collisionBoundary | union | 'clipping-ancestors' | The boundary that the tooltip element should be constrained to. |
collisionPadding | union | 5 | The padding between the tooltip element and the edges of the collision boundary to add whitespace between them to prevent them from touching. |
container | union | The container element the tooltip positioner is appended to. | |
keepMounted | bool | false | Whether the tooltip remains mounted in the DOM while closed. |
positionMethod | enum | 'absolute' | The CSS position strategy for positioning the tooltip element. |
render | union | A function to customize rendering of the component. | |
side | enum | 'top' | The side of the anchor element that the tooltip element should be placed at. |
sideOffset | number | 0 | The gap between the anchor element and the tooltip element. |
sticky | bool | false | Whether to allow the tooltip to remain stuck in view while the anchor element is scrolled out of view. |
TooltipPopup
The tooltip popup element.
Prop | Type | Default | Description |
---|---|---|---|
className | union | Class names applied to the element or a function that returns them based on the component's state. | |
render | union | A function to customize rendering of the component. |
TooltipArrow
Renders an arrow that points to the center of the anchor element.
Prop | Type | Default | Description |
---|---|---|---|
className | union | Class names applied to the element or a function that returns them based on the component's state. | |
hideWhenUncentered | bool | false | If true , the arrow will be hidden when it can't point to the center of the anchor element. |
render | union | A function to customize rendering of the component. |