Animate UI's own tooltip: the tooltips of a provider share one popup that moves from trigger to trigger instead of closing and reopening.
Installation
The popup is positioned with Floating UI, a peer dependency of this primitive:
Usage
import {
Tooltip,
TooltipArrow,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@animate-ui/react/primitives/tooltip';
export function Toolbar() {
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<button aria-label="Bold">B</button>
</TooltipTrigger>
<TooltipContent className="rounded-md bg-foreground px-3 py-1.5 text-xs text-background">
<TooltipArrow className="size-2.5 fill-foreground" />
<p>Bold</p>
</TooltipContent>
</Tooltip>
<Tooltip side="bottom">
<TooltipTrigger asChild>
<button aria-label="Italic">I</button>
</TooltipTrigger>
<TooltipContent className="rounded-md bg-foreground px-3 py-1.5 text-xs text-background">
<TooltipArrow className="size-2.5 fill-foreground" />
<p>Italic</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}Unlike the library tooltips, this one is Animate UI's own: no Radix UI, Base UI or React Aria underneath. TooltipContent renders nothing where it is written: it declares what the provider's single popup shows when its trigger is hovered or focused. Its content is laid out in a layer inside the popup (which takes the popup's display, flex and grid layout), and its TooltipArrow is drawn by the popup itself, outside that layer. The layer keeps the content from stretching while the popup resizes between triggers: no layout prop is needed on it (one would correct it twice).
Animation
The motion of Animate UI v1's Tooltip, on a spring a touch calmer than the library tooltips':
- The popup grows from 0.7 and fades in from the point facing its trigger, like the Radix UI, Base UI and React Aria tooltips, on a spring as quick as theirs but a touch calmer (stiffness 400, damping 31: it goes about 2% past its size before it settles, theirs about 4%), and goes back the same way when it closes.
- Hovering the next trigger while a tooltip is showing doesn't close it: the popup and its arrow glide to the new trigger and resize to the new content (a shared layout animation), on the same spring. Right after a tooltip closes (within
closeDelay), the next one opens without theopenDelay. - Meanwhile its content changes in the way the popup goes, like a tab panel: the previous label leaves the other way into a 6px blur, fading out and sliding 40px in 0.13s, and the next one slides 40px in from the side the popup goes to, out of the blur, showing from the start of its slide (0.34s, so its slide outlasts the popup's glide, which goes the other way on screen). Moving right, the previous label leaves to the left and the next comes from the right; moving up, the previous one goes down and the next comes from above. It follows the triggers on screen, so right to left and stacked triggers too. Neither label is ever stretched by the popup resizing: both are shown at their size, around the point the popup grows from, and clipped by the popup. The arrow only glides. Going back and forth faster than that, a label coming back goes on from where it is, and the one that was coming in leaves from where it is. Nothing of this on the first open or the close.
- With
asChild, the same happens inside the child, which is the popup. <AnimateUIProvider tempo>paces the spring and the swap.- Under reduced motion, the popup only fades, and moving between triggers jumps, its content changing at once.
API Reference
TooltipProvider
Holds the shared popup. Every Tooltip must be inside one.
| Prop | Type | Default |
|---|---|---|
children | React.ReactNode | - |
id? | string | - |
openDelay? | number | 0 |
closeDelay? | number | 300 |
transition? | Transition | { type: 'spring', stiffness: 400, damping: 31 } |
Tooltip
| Prop | Type | Default |
|---|---|---|
children | React.ReactNode | - |
side? | "top" | "bottom" | "left" | "right" | "top" |
sideOffset? | number | 10 |
align? | "start" | "center" | "end" | "center" |
alignOffset? | number | 0 |
TooltipTrigger
Opens the tooltip on hover and focus, and closes it on leave, blur, a press, Escape, scroll and resize. While open, it gets aria-describedby pointing at the popup (role="tooltip").
| Prop | Type | Default |
|---|---|---|
asChild? | boolean | false |
...props? | HTMLMotionProps<'div'> | - |
TooltipContent
| Prop | Type | Default |
|---|---|---|
asChild? | boolean | false |
...props? | HTMLMotionProps<'div'> | - |
TooltipArrow
Place it inside TooltipContent.
| Prop | Type | Default |
|---|---|---|
withTransition? | boolean | true |
...props? | Omit<React.ComponentProps<typeof motion.create(FloatingArrow)>, 'context'> | - |
useGlobalTooltip, useTooltip
Return the provider's and the tooltip's state: the current tooltip, showTooltip, hideTooltip, hideImmediate, the transition and the reference element; the tooltip's props, side, alignment, offsets and id.