import { ArchiveIcon, CopyIcon, PencilIcon, StarIcon, Trash2Icon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
const actions = [
{ label: "Edit", icon: PencilIcon },
{ label: "Duplicate", icon: CopyIcon },
{ label: "Star", icon: StarIcon },
{ label: "Archive", icon: ArchiveIcon },
{ label: "Delete", icon: Trash2Icon },
];
export default function TooltipIconButtons() {
return (
<div className="flex items-center gap-1">
{actions.map(({ label, icon: Icon }) => (
<Tooltip key={label}>
<TooltipTrigger render={<Button variant="ghost" size="icon" aria-label={label} />}>
<Icon />
</TooltipTrigger>
<TooltipContent>{label}</TooltipContent>
</Tooltip>
))}
</div>
);
}- Add the registryOnce per project
pnpm dlx shadcn@latest registry add @jarvis=https://ui.jarv.is/r/{name}.json - Add tooltip
pnpm dlx shadcn@latest add @jarvis/tooltip
Usage
Wrap your app in a TooltipProvider once, near its root.
import type { ReactNode } from "react";
import { TooltipProvider } from "@/components/ui/tooltip";
export function App({ children }: { children: ReactNode }) {
return <TooltipProvider>{children}</TooltipProvider>;
}Then give each control its own tooltip.
import { PencilIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
export function EditButton() {
return (
<Tooltip>
<TooltipTrigger render={<Button variant="ghost" size="icon" aria-label="Edit" />}>
<PencilIcon />
</TooltipTrigger>
<TooltipContent>Edit</TooltipContent>
</Tooltip>
);
}Screen readers ignore tooltips, so the trigger still needs its own accessible name, like an
aria-label. Don't put anything essential in a tooltip. For an info icon, use a Popover
infotip instead.
Composition
Examples
With a shortcut
import { SearchIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Kbd, KbdGroup } from "@/components/ui/kbd";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
export default function TooltipWithShortcut() {
return (
<Tooltip>
<TooltipTrigger render={<Button variant="secondary" size="icon" aria-label="Search" />}>
<SearchIcon />
</TooltipTrigger>
<TooltipContent>
Search
<KbdGroup>
<Kbd>⌘</Kbd>
<Kbd>K</Kbd>
</KbdGroup>
</TooltipContent>
</Tooltip>
);
}Sides
side and align on TooltipContent set its position.
import { Button } from "@/components/ui/button";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
const sides = ["top", "right", "bottom", "left"] as const;
export default function TooltipSides() {
return (
<div className="flex flex-wrap items-center gap-3">
{sides.map((side) => (
<Tooltip key={side}>
<TooltipTrigger render={<Button variant="outline" size="sm" className="capitalize" />}>
{side}
</TooltipTrigger>
<TooltipContent side={side}>Shown on the {side}</TooltipContent>
</Tooltip>
))}
</div>
);
}Disabled triggers
A tooltip can explain why a control is disabled. Add focusableWhenDisabled so the button still
gets focus and taps, and give it the reason in aria-description for screen readers.
import { Button } from "@/components/ui/button";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
export default function TooltipDisabled() {
return (
<Tooltip>
<TooltipTrigger
render={
<Button
variant="primary"
disabled
focusableWhenDisabled
aria-description="Add a title to publish"
/>
}
>
Publish
</TooltipTrigger>
<TooltipContent>Add a title to publish</TooltipContent>
</Tooltip>
);
}Opening on tap
Tooltips don't open on touch screens unless the trigger is disabled. openOnTap opens one on tap
anyway, and openOnTap={false} turns it off for a disabled trigger.
Edited
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
export default function TooltipOpenOnTap() {
return (
<p className="text-sm text-muted-foreground">
Edited{" "}
<Tooltip>
<TooltipTrigger
openOnTap
render={<time dateTime="2026-03-04T15:12" />}
className="underline decoration-border-strong decoration-dotted underline-offset-2"
>
2 hours ago
</TooltipTrigger>
<TooltipContent>March 4, 2026 at 3:12 PM</TooltipContent>
</Tooltip>
</p>
);
}Props
The tables leave out className, style and render. Parts built on Base UI take them as its styling and composition guides describe.
TooltipProvider
| Prop | Type | Default |
|---|---|---|
children | React.ReactNode | — |
delayHow long to wait before opening the tooltip on hover. Specified in milliseconds. | number | 0 |
closeDelayHow long to wait before closing a tooltip. Specified in milliseconds. | number | — |
timeoutAnother tooltip will open instantly if the previous tooltip is closed within this timeout. Specified in milliseconds. | number | 400 |
Tooltip
| Prop | Type | Default |
|---|---|---|
defaultOpenWhether the tooltip is initially open. To render a controlled tooltip, use the | boolean | false |
openWhether the tooltip is currently open. | boolean | — |
onOpenChangeEvent handler called when the tooltip is opened or closed. | ((open: boolean, eventDetails: Tooltip.Root.ChangeEventDetails) => void) | — |
onOpenChangeCompleteEvent handler called after any animations complete when the tooltip is opened or closed. | ((open: boolean) => void) | — |
disableHoverablePopupWhether the tooltip contents can be hovered without closing the tooltip. | boolean | false |
trackCursorAxisDetermines which axis the tooltip should track the cursor on. | "both" | "none" | "x" | "y" | 'none' |
actionsRefA ref to imperative actions.
| React.RefObject<TooltipRootActions | null> | — |
disabledWhether the tooltip is disabled. | boolean | false |
handleA handle to associate the tooltip with a trigger. If specified, allows external triggers to control the tooltip's open state. Can be created with the Tooltip.createHandle() method. | Tooltip.Handle<unknown> | — |
childrenThe content of the tooltip.
This can be a regular React node or a render function that receives the | PayloadChildRenderFunction<unknown> | React.ReactNode | — |
triggerIdID of the trigger that the tooltip is associated with.
This is useful in conjunction with the | string | null | — |
defaultTriggerIdID of the trigger that the tooltip is associated with.
This is useful in conjunction with the | string | null | — |
TooltipTrigger
| Prop | Type | Default |
|---|---|---|
handleA handle to associate the trigger with a tooltip. | Tooltip.Handle<unknown> | — |
payloadA payload to pass to the tooltip when it is opened. | unknown | — |
delayHow long to wait before opening the tooltip on hover. Specified in milliseconds. | number | 600 |
closeOnClickWhether the tooltip should close when this trigger is clicked. | boolean | true |
closeDelayHow long to wait before closing the tooltip. Specified in milliseconds. | number | 0 |
disabledIf | boolean | false |
openOnTapOpens the tooltip when the trigger is tapped on a touch screen, where hovering can't open
it. A second tap or a tap elsewhere closes it. Defaults to | boolean | — |
TooltipContent
| Prop | Type | Default |
|---|---|---|
anchorAn element to position the popup against. By default, the popup will be positioned against the trigger. | Element | React.RefObject<Element | null> | VirtualElement | (() => Element | VirtualElement | null) | null | — |
sideOffsetDistance between the anchor and the popup in pixels. Also accepts a function that returns the distance to read the dimensions of the anchor and positioner elements, along with its side and alignment. The function takes a
| number | OffsetFunction | 6 |
alignHow to align the popup relative to the specified side. | "center" | "end" | "start" | "center" |
alignOffsetAdditional offset along the alignment axis in pixels. Also accepts a function that returns the offset to read the dimensions of the anchor and positioner elements, along with its side and alignment. The function takes a
| number | OffsetFunction | 0 |
sideWhich side of the anchor element to align the popup against. May automatically change to avoid collisions. | "bottom" | "inline-end" | "inline-start" | "left" | "right" | "top" | "top" |