Tooltip
FreeA short hint that appears on hover or focus, never the only way to learn something.
pnpm dlx shadcn@latest add https://caliperui.site/r/tooltip.jsonThe first tooltip waits 300 ms; sweep along and the next ones appear at once.
When to use #
Use it for
- Naming an icon-only button.
- Showing a keyboard shortcut or a short clarification.
Skip it when
- The text is long or interactive; use a popover or hover card.
- Touch-only contexts where hover does not exist.
Decisions #
Why it is built this way, so you know what to keep when you change your copy.
Behaviour
- The first waits, neighbours follow at once
- The first tooltip waits 300ms; moving along a toolbar shows the next immediately for half a second, so scanning icons does not mean waiting at each one.
- The label cannot be hovered
- Tooltip content ignores the pointer, so moving across a toolbar switches labels instead of getting stuck on the last one.
Usage
- Never the only explanation
- Touch screens have no hover. Anything people need to complete a task belongs on the page, not in a tooltip.
Installation #
pnpm dlx shadcn@latest add https://caliperui.site/r/tooltip.json
Adds its 2 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.
Usage #
This is the code behind the preview above. It is built from Tooltip, TooltipTrigger, TooltipProvider and TooltipContent.
import { Bell, FileText, Inbox, Upload } from "lucide-react" import { Button } from "@/components/ui/button" import { ButtonGroup } from "@/components/ui/button-group" import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip" export function IconTooltip() { const tools = [ { label: "Duplicate", shortcut: "⌘D", icon: <FileText aria-hidden="true" /> }, { label: "Download PDF", shortcut: "⌘⇧S", icon: <Upload aria-hidden="true" className="rotate-180" /> }, { label: "Send to customer", shortcut: "⌘↵", icon: <Inbox aria-hidden="true" /> }, { label: "Notifications", shortcut: "⌘N", icon: <Bell aria-hidden="true" /> }, ]; return ( <TooltipProvider> <div className="grid justify-items-center gap-3 pt-10"> <ButtonGroup aria-label="Invoice tools"> {tools.map((tool) => ( <Tooltip key={tool.label}> <TooltipTrigger asChild> <Button variant="secondary" size="icon" aria-label={tool.label} aria-keyshortcuts={tool.shortcut}>{tool.icon}</Button> </TooltipTrigger> <TooltipContent shortcut={tool.shortcut}>{tool.label}</TooltipContent> </Tooltip> ))} </ButtonGroup> <p className="text-xs text-muted-foreground">The first tooltip waits 300 ms; sweep along and the next ones appear at once.</p> </div> </TooltipProvider> ); }
API reference #
Tooltip
component
No props of its own; it accepts the native props of its root element.
TooltipTrigger
component
No props of its own; it accepts the native props of its root element.
TooltipProvider
component
No props of its own; it accepts the native props of its root element.
TooltipContent
component
Also accepts the native props of its root element and a ref.
Accessibility #
It is built on native elements, so roles and focus come from the browser.
Keyboard and focus handling comes from Radix tooltip: arrow keys move through items, Enter and Space select, and Escape closes and returns focus.
Motion #
It declares no animation in its component source. Any transition comes from its stylesheet, which uses the duration tokens that collapse to instant under prefers-reduced-motion.