Tooltip
A tooltip is a short label that appears next to an element when you hover or focus it. It names things that have no room for a visible label, like icon buttons.
import { Bold, Italic, Underline } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Kbd } from "@/components/ui/kbd";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
export default function TooltipDemo() {
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger render={<Button variant="secondary" size="icon" aria-label="Bold" />}>
<Bold />
</TooltipTrigger>
<TooltipContent>Bold</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger render={<Button variant="secondary" size="icon" aria-label="Italic" />}>
<Italic />
</TooltipTrigger>
<TooltipContent>Italic</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger render={<Button variant="secondary" size="icon" aria-label="Underline" />}>
<Underline />
</TooltipTrigger>
<TooltipContent>Underline</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger render={<Button />}>Save</TooltipTrigger>
<TooltipContent className="flex items-center gap-2">
Save changes
<Kbd aria-label="Command">⌘</Kbd>
<Kbd>S</Kbd>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}The code
The component is a single file, read from the repository when the site is built. Press Show code to see all of it.
import type { ComponentProps } from "react";
import { Tooltip as BaseTooltip } from "@base-ui/react/tooltip";
import { cx } from "@/lib/cx";
import { popupMotion, popupSurface } from "@/lib/styles";
import type { WithClass } from "@/lib/types";
// Wrap part of the page (or all of it) in a TooltipProvider and tooltips open without their
// usual delay once one has opened, so moving along a toolbar feels quick.
export const TooltipProvider = BaseTooltip.Provider;
export const Tooltip = BaseTooltip.Root;
// Renders a button by default. To put a tooltip on your own element, such as a Button, pass it
// as render: <TooltipTrigger render={<Button />}>.
export const TooltipTrigger = BaseTooltip.Trigger;
// side, sideOffset and align place the tooltip against its trigger; every other prop goes to the
// popup itself.
export function TooltipContent({
side = "top",
sideOffset = 6,
align,
className,
children,
...props
}: WithClass<ComponentProps<typeof BaseTooltip.Popup>> &
Pick<ComponentProps<typeof BaseTooltip.Positioner>, "side" | "sideOffset" | "align">) {
return (
<BaseTooltip.Portal>
<BaseTooltip.Positioner side={side} sideOffset={sideOffset} align={align} className="z-50">
<BaseTooltip.Popup className={cx(popupSurface, popupMotion, "px-2 py-1 text-sm", className)} {...props}>
{children}
</BaseTooltip.Popup>
</BaseTooltip.Positioner>
</BaseTooltip.Portal>
);
}Props
| Prop | Type | Default | What it does |
|---|---|---|---|
side | "top" | "right" | "bottom" | "left" | "top" | On TooltipContent. Which side of the trigger it opens on. It flips to the other side if there is no room. |
align | "start" | "center" | "end" | "center" | On TooltipContent. How it lines up along that side. |
sideOffset | number | 6 | On TooltipContent. The gap in pixels between the trigger and the tooltip. |
delay | number | 600 | On TooltipTrigger. Milliseconds to wait after hovering before it opens. |
closeDelay | number | 0 | On TooltipTrigger. Milliseconds to wait after leaving before it closes. |
render | element | — | On TooltipTrigger. Puts the tooltip on your own element, such as a Button, instead of a plain button. |
Wrap the part of the page that has tooltips in a TooltipProvider. Every other prop goes to the Base UI Tooltip underneath.
The thinking
A tooltip adds one thing: a name. If it needs a sentence, a button or a link, use a Popover instead.
- Built on Base UI: Base UI handles the hover delay, the keyboard focus, the collision-aware placement and the Escape key.
- Provider: Inside a TooltipProvider, once one tooltip has opened the next ones open without the delay. Sliding along a toolbar feels quick instead of sticky.
- Your own trigger: Pass render to put the tooltip on a Button, as the demo does. The button keeps its own look and behavior.
Interface design
- Small: Small text, snug padding, the shared overlay surface and no arrow. The gap to the trigger is enough to show what it belongs to.
- Shortcuts: A Kbd can sit after the label to show the shortcut, as in the Save example.
- Above everything: It floats above the page and the sticky header.
Interaction
- Hover: It opens after a short pause, so moving the pointer across a toolbar doesn't flash tooltips.
- Focus: It opens at once when the trigger receives keyboard focus.
- Dismiss: Escape, moving the pointer away, moving focus away or clicking the trigger closes it.
- Motion: A 150ms fade and a small scale from the side it opens from. With reduced motion turned on it appears at once.
Empathy
- Not the only label: A tooltip doesn't appear on touch screens. An icon-only button still needs its own
aria-label, as the demo does. - Keyboard users see it: It opens on focus, not only on hover.
- Don't hide what matters: Use a tooltip to repeat or sharpen a name, never to hold information people need to finish a task.