A tooltip is a small popup that displays brief, supplementary information when hovering over or focusing on an element. It’s useful for providing context or explanations without cluttering the interface.
Installation
npx shadcn@latest add @eo-n/tooltip
Install dependencies
npm install @base-ui/react lucide-react
Copy component code
Copy and paste the following code into components/ui/tooltip.tsx:"use client";
import * as React from "react";
import { Tooltip as TooltipPrimitive } from "@base-ui/react";
import { ChevronDown } from "lucide-react";
import { cn } from "@/lib/utils";
const TooltipCreateHandle = TooltipPrimitive.createHandle;
function TooltipProvider({
delay = 0,
...props
}: TooltipPrimitive.Provider.Props) {
return (
<TooltipPrimitive.Provider
data-slot="tooltip-provider"
delay={delay}
{...props}
/>
);
}
const Tooltip = TooltipPrimitive.Root;
function TooltipTrigger({
...props
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
}
function TooltipPortal({
...props
}: React.ComponentProps<typeof TooltipPrimitive.Portal>) {
return <TooltipPrimitive.Portal data-slot="tooltip-portal" {...props} />;
}
interface TooltipContentProps
extends Omit<
React.ComponentProps<typeof TooltipPrimitive.Positioner>,
"render"
> {}
function TooltipContent({
className,
sideOffset = 10,
children,
...props
}: TooltipContentProps) {
return (
<TooltipPortal>
<TooltipPrimitive.Positioner
data-slot="tooltip-positioner"
sideOffset={sideOffset}
className="z-50 transition-[top,left,right,bottom,transform] duration-150 ease-out"
{...props}
>
<TooltipPrimitive.Popup
data-slot="tooltip-popup"
className={cn(
"bg-primary text-primary-foreground relative rounded-md text-xs text-balance transition-[width,height,opacity,scale] duration-150 ease-out",
"origin-[var(--transform-origin)] data-[ending-style]:scale-95 data-[ending-style]:opacity-0 data-[starting-style]:scale-95 data-[starting-style]:opacity-0",
className
)}
>
<div className="px-2 py-1">{children}</div>
<TooltipPrimitive.Arrow className="transition-[left] duration-150 ease-out">
<ChevronDown className="fill-primary stroke-primary size-4 shrink-0" />
</TooltipPrimitive.Arrow>
</TooltipPrimitive.Popup>
</TooltipPrimitive.Positioner>
</TooltipPortal>
);
}
export {
TooltipCreateHandle,
TooltipProvider,
Tooltip,
TooltipTrigger,
TooltipPortal,
TooltipContent,
};
Update imports
Update the import paths to match your project setup.
Wrap your application with the TooltipProvider to enable tooltips:
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
<TooltipProvider>
<Tooltip>
<TooltipTrigger>Hover me</TooltipTrigger>
<TooltipContent>
<p>Tooltip content</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
Examples
Basic Tooltip
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { Button } from "@/components/ui/button";
export default function TooltipDemo() {
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="outline">Hover me</Button>
</TooltipTrigger>
<TooltipContent>
<p>This is a tooltip</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}
With Icon
Use tooltips to provide context for icon buttons:
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { Button } from "@/components/ui/button";
import { Info } from "lucide-react";
export default function TooltipIcon() {
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="icon">
<Info className="h-4 w-4" />
<span className="sr-only">More information</span>
</Button>
</TooltipTrigger>
<TooltipContent>
<p>This provides additional information</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}
Different Positions
Position the tooltip on different sides:
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { Button } from "@/components/ui/button";
export default function TooltipPositions() {
return (
<TooltipProvider>
<div className="flex gap-4">
<Tooltip>
<TooltipTrigger asChild>
<Button>Top</Button>
</TooltipTrigger>
<TooltipContent side="top">
<p>Tooltip on top</p>
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button>Bottom</Button>
</TooltipTrigger>
<TooltipContent side="bottom">
<p>Tooltip on bottom</p>
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button>Left</Button>
</TooltipTrigger>
<TooltipContent side="left">
<p>Tooltip on left</p>
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button>Right</Button>
</TooltipTrigger>
<TooltipContent side="right">
<p>Tooltip on right</p>
</TooltipContent>
</Tooltip>
</div>
</TooltipProvider>
);
}
With Delay
Control the delay before the tooltip appears:
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { Button } from "@/components/ui/button";
export default function TooltipDelay() {
return (
<div className="space-y-4">
<TooltipProvider delay={0}>
<Tooltip>
<TooltipTrigger asChild>
<Button>No delay</Button>
</TooltipTrigger>
<TooltipContent>
<p>Appears immediately</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<TooltipProvider delay={500}>
<Tooltip>
<TooltipTrigger asChild>
<Button>500ms delay</Button>
</TooltipTrigger>
<TooltipContent>
<p>Appears after 500ms</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<TooltipProvider delay={1000}>
<Tooltip>
<TooltipTrigger asChild>
<Button>1s delay</Button>
</TooltipTrigger>
<TooltipContent>
<p>Appears after 1 second</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
);
}
Rich Content
Tooltips can contain rich content:
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { Button } from "@/components/ui/button";
export default function TooltipRichContent() {
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="outline">View details</Button>
</TooltipTrigger>
<TooltipContent className="max-w-xs">
<div className="space-y-2">
<p className="font-semibold">Product Information</p>
<p className="text-xs">
This product includes advanced features and premium support.
</p>
</div>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}
Multiple Tooltips
Share a TooltipProvider across multiple tooltips:
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { Button } from "@/components/ui/button";
export default function MultipleTooltips() {
return (
<TooltipProvider>
<div className="flex gap-4">
<Tooltip>
<TooltipTrigger asChild>
<Button>Save</Button>
</TooltipTrigger>
<TooltipContent>
<p>Save your changes</p>
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="outline">Cancel</Button>
</TooltipTrigger>
<TooltipContent>
<p>Discard changes</p>
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="destructive">Delete</Button>
</TooltipTrigger>
<TooltipContent>
<p>Permanently delete</p>
</TooltipContent>
</Tooltip>
</div>
</TooltipProvider>
);
}
API Reference
TooltipProvider
Wrapper component that provides tooltip context. Should wrap all tooltips in your app.
The delay in milliseconds before the tooltip appears.
How long a user has to enter another tooltip before the delay is ignored.
Tooltip
The root component that manages the tooltip state.
Controls the open state of the tooltip.
The initial open state for uncontrolled usage.
Callback fired when the open state changes.
TooltipTrigger
The element that triggers the tooltip.
Merge props onto the child element instead of wrapping it.
TooltipContent
The content to be rendered in the tooltip.
side
'top' | 'right' | 'bottom' | 'left'
default:"'top'"
The preferred side of the trigger to render against when open.
The distance in pixels from the trigger.
align
'start' | 'center' | 'end'
default:"'center'"
The preferred alignment against the trigger.
An offset in pixels from the alignment.
Additional CSS classes to apply.
TooltipCreateHandle
Creates a handle for imperative tooltip control.
const handleRef = TooltipCreateHandle();
// Later, use the handle to control the tooltip
handleRef.current?.open();
handleRef.current?.close();
Accessibility
- Tooltips are keyboard accessible and work with screen readers
- The tooltip content is associated with the trigger via
aria-describedby
- Pressing Escape will close an open tooltip
- Tooltips automatically position themselves to stay within the viewport