The shadcn/ui Tooltip component displays contextual information when users hover over or focus on a UI element. It is built on Radix UI primitives, ships with full keyboard and screen reader accessibility out of the box, and is installed in one command: npx shadcn@latest add tooltip. Every tooltip requires a <TooltipProvider> in your root layout and composes three sub-components: Tooltip, TooltipTrigger, and TooltipContent.
In this guide, I'll walk through 10 practical shadcn tooltip variants — from the basic implementation to keyboard shortcut hints, disabled button tooltips, rich content, form validation, and custom styling. Each example includes a working code snippet you can drop straight into a Next.js project.
Prerequisites
Before you build with the shadcn tooltip, you need a working shadcn/ui setup with the tooltip component added. Here is what your project needs:
- React 18+
- Next.js 13+ (App Router recommended — if you're starting from scratch, our guide to building with Next.js and shadcn/ui covers the full project setup)
- Tailwind CSS configured
- shadcn/ui installed
Install the tooltip component with the CLI:
Then add <TooltipProvider> to your root layout. This is a required step — tooltips will not render without it:
One important note: avoid wrapping each individual tooltip with its own <TooltipProvider>. That pattern creates redundant providers on every render and causes a noticeable performance hit in apps with many tooltips. One provider at the root handles everything.
Basic Implementation
The base shadcn tooltip wraps a trigger element and shows content on hover or keyboard focus. This is the three-part composition you will use for every variant in this guide.
What you get:
A fully accessible tooltip that appears on hover and keyboard focus, disappears on mouse-out or blur, and is announced by screen readers via the correct ARIA role.
asChildonTooltipTriggerrenders the button as the trigger instead of wrapping it in an extra elementTooltipContentaccepts any valid JSX, not just text- The default
delayDurationis0ms — the tooltip appears immediately
10 shadcn Tooltip Variants (with Code Examples)
These patterns cover the full range of real use cases: positioning, keyboard hints, disabled elements, delays, rich content, icons, avatars, form validation, and custom styling.
1. Tooltip Positioning (top, right, bottom, left)
Control which side the tooltip appears on using the side prop on TooltipContent. The four values are top, right, bottom, and left. Radix UI automatically flips the tooltip if it would overflow the viewport.
When to use:
Use side="top" for toolbar icons, side="right" for sidebar items, and side="bottom" for table actions where upward overflow is a concern.
2. Tooltip with Keyboard Shortcut
A keyboard shortcut tooltip shows the hotkey that triggers an action alongside the action label. This pattern is common in editors, design tools, and dashboards with power users. Use <kbd> elements inside TooltipContent to render styled key indicators.
When to use:
Any toolbar button, command palette trigger, or action with a known keyboard shortcut. The <kbd> elements are semantic HTML — screen readers announce them as keyboard input.
3. Tooltip on a Disabled Button
Disabled elements do not fire pointer events, so a tooltip on a disabled button will not appear by default. The fix is to wrap the disabled button in a <span> and use that span as the trigger. This gives the tooltip a live pointer target while keeping the button disabled.
When to use:
Form submit buttons, actions that require a completed step, or anything gated behind a condition. Always explain why the button is disabled — that is the entire point of this pattern.
4. Tooltip with Custom Delay
The delayDuration prop on <TooltipProvider> sets the millisecond delay before any tooltip in the tree appears. The default is 0. For information-dense dashboards, a 300–500 ms delay prevents tooltips from appearing on every accidental hover.
When to use:
Use delayDuration={0} for icon-only buttons where the label is critical. Use delayDuration={300} or higher for supplementary hints on elements that already have visible labels.
5. Tooltip with Rich Content
TooltipContent accepts any JSX — not just a string. A rich content tooltip can include a title, description, badge, or icon combination. Keep content short enough to scan in under 2 seconds; if it needs more space, a Popover is the right component instead.
When to use:
Help icons next to pricing tiers, feature flags, or settings fields where a short label isn't enough. Add max-w-[200px] on TooltipContent to keep it readable.
6. Tooltip on an Icon Button
Icon-only buttons have no visible label, making them inaccessible without additional context. A shadcn tooltip on an icon button serves as the visible label. Always pair it with a <span className="sr-only"> inside the button for screen readers, and keep the tooltip text identical to the action.
When to use:
Toolbar action rows in dashboards, table row actions, header icon navigation. This is the most common real-world tooltip pattern in SaaS products. If you're building a full dashboard layout, see our resizable sidebar component guide for the surrounding structure these icon toolbars live in.
7. Tooltip on an Avatar
An avatar tooltip shows user metadata — full name, role, or status — when hovering over a compact avatar display. This is especially useful in team dashboards, comment threads, or any UI where avatars stand in for full user cards.
When to use:
Stacked avatar rows showing team members, collaborators on a file, or people assigned to a task. Pairs well with the shadcn avatar component guide for fuller avatar implementation patterns.
8. Tooltip for Form Validation Errors
A validation tooltip surfaces an inline error message next to the field that caused it, without breaking the layout with an error block below every input. Use open and onOpenChange in controlled mode to show the tooltip only when a field has an error.
When to use:
Compact forms where error messages below each field would collapse the layout. Use open={!!error} for controlled display — the tooltip only appears when the error state is truthy.
Pro tip: This pattern pairs well with shadcn select fields too. Dropdown validation errors benefit from the same tooltip approach. See the shadcn select component guide for form composition patterns.
9. Custom Styled Tooltip
The default shadcn tooltip uses bg-primary text-primary-foreground for its content background. You can override this per-tooltip using Tailwind utility classes directly on TooltipContent. Custom styled tooltips are useful for status indicators, warnings, and contextual colour coding.
When to use:
Status badges, health indicators, and alert contexts where colour communicates urgency. Match the tooltip colour to the element it describes — don't use red tooltips on green status indicators.
10. Controlled Tooltip (programmatic open/close)
By default the shadcn tooltip is uncontrolled — it opens on hover and closes on mouse-out. A controlled tooltip uses open and onOpenChange props on the <Tooltip> root to manage visibility from your component's state. This is useful for tutorial flows, onboarding hints, and async-triggered feedback.
When to use:
Copy-to-clipboard feedback, onboarding steps that highlight a feature, or contextual hints in a chat interface where message actions appear on hover. The setTimeout pattern auto-dismisses after a set duration.
Animated Tooltip Variants
The default shadcn tooltip already animates — it uses animate-in fade-in-0 zoom-in-95 on open and animate-out fade-out-0 zoom-out-95 on close. You can replace that animation entirely by passing a Tailwind arbitrary animate-[...] class to TooltipContent. Since className is merged after the defaults via cn(), the later class wins and replaces the built-in animation.
Define your @keyframes in a <style> tag inside the component (they are global once rendered) and reference them by name in the arbitrary value. Four variants that cover the most useful motion patterns:
How each variant works:
- Bounce — scales past 1.0 at 55% then settles back, giving a physical spring feel
- Slide Up — pure
translateYwith no scale, clean and minimal - Elastic — same overshoot pattern but driven by a spring
cubic-bezier(0.34, 1.56, 0.64, 1)passed viastyle(avoids Tailwind's arbitrary value parser struggling with parentheses in the shorthand) - Drift — 4 px vertical translate with a gentle ease, the subtlest option for content-heavy UIs where motion should not compete for attention
Note: These custom animations replace the default open animation only. The close animation (data-[state=closed]:animate-out) remains intact. If you want to animate close too, add a corresponding data-[state=closed]:animate-[...] class alongside the open one.
Cursor-Following Tooltip
The shadcn TooltipContent is positioned relative to the trigger via Radix UI's Popper primitive, so it cannot track the mouse pointer directly. To get a tooltip that follows the cursor as it moves, build a small custom component with framer-motion: track onMouseMove coordinates with useMotionValue, smooth them with useSpring, and fade the tooltip in and out with AnimatePresence.
How it works:
onMouseMovereads the cursor position relative to the container withgetBoundingClientRect()and writes it to twouseMotionValueinstancesuseSpringwraps each motion value so the tooltip glides toward the cursor instead of snapping to it on every pixel of movementx: "-50%", y: "calc(-100% - 12px)"centers the tooltip horizontally above the cursor with a small gapAnimatePresencehandles the enter/exit fade so the tooltip appears on hover and disappears cleanly when the pointer leaves
When to use:
Image galleries, canvas or chart annotations, and creative portfolio sites where a label should track a large hover target rather than stay pinned to one corner. For standard buttons, icons, and form fields, the built-in TooltipContent positioning from the variants above is simpler and more accessible — reach for a cursor-following tooltip only when the visual effect is the point.
Animated Avatar Group Tooltip
A popular pattern for "who's on this team" or "who worked on this" displays — a stacked group of avatars where hovering one springs up a name-and-role card that tilts based on where the cursor enters the avatar. It is built the same way as the cursor-following tooltip: framer-motion drives the position and rotation, but here the values come from the cursor's x offset within each avatar rather than from the page coordinates.
How it works:
onMouseMovetracks the cursor's horizontal offset from the center of the avatar usingevent.nativeEvent.offsetXminus half the element's widthuseTransformmaps that offset to a rotation range (-45degto45deg) and a horizontal shift (-50pxto50px), so the card tilts toward the side the cursor entered fromuseSpringsmooths both values for a natural, slightly bouncy tilt instead of an instant snapAnimatePresencewithmode="popLayout"handles the spring-in/scale-out transition as the user moves between avatarsgroup-hover:z-30 group-hover:scale-105lifts the hovered avatar above its neighbours in the stack
When to use:
Team pages, "contributors" lists, testimonial avatar groups, and project collaborator stacks. Pairs with the shadcn avatar component guide for the underlying Avatar setup. As with the cursor-following tooltip, this is a decorative custom component rather than the accessible Tooltip primitive — keep the avatar's alt text or an aria-label in place so screen reader users still get the name and role.
Common Issues with shadcn Tooltip (and Fixes)
The shadcn tooltip is straightforward to implement, but five issues appear consistently in real apps. Here is what causes each one and how to fix it.
1. Tooltip does not show on hover
The most common cause is a missing <TooltipProvider> in the root layout. Tooltips will not open without a provider in the tree.
Fix: Add <TooltipProvider> to app/layout.tsx wrapping { children }. One provider at the root is enough for the entire app.
2. Tooltip does not show on a disabled button
Disabled HTML elements suppress pointer events, so the hover that triggers the tooltip never fires.
Fix: Wrap the disabled button in a <span tabIndex={0}> and use the span as the TooltipTrigger. Add pointer-events-none to the button itself to keep it unclickable.
3. Performance slowdown with many tooltips
Wrapping each individual tooltip in its own <TooltipProvider> creates a new provider instance on every render. In one documented case, 60 tooltip instances caused a measurable performance regression.
Fix: Use a single <TooltipProvider> at the app root. Never put a provider inside a loop or a component that renders repeatedly.
4. Tooltip clips at the viewport edge
Radix UI automatically repositions the tooltip to avoid overflow, but this requires the trigger element to be inside a correctly positioned container.
Fix: Use sideOffset on TooltipContent to add spacing between the tooltip and the trigger, and rely on the side prop to set your preferred direction. Radix handles the fallback automatically.
5. Tooltip does not close after clicking a trigger
Uncontrolled tooltips stay open when the trigger is clicked if click does not move focus away from the element.
Fix: Switch to controlled mode with open and onOpenChange, and set open={false} on click. Or use disableHoverableContent on the provider to prevent the tooltip from staying open when the user moves to the tooltip content itself.
When to use a shadcn Tooltip vs a Popover
The tooltip and popover look similar but serve different purposes. A tooltip is non-interactive — it displays supplementary text and disappears. A popover is interactive — it can contain buttons, links, forms, and persistent content.
Use a Tooltip when:
- The content is 1–2 lines of non-interactive text
- The hint supplements a visible element (icon button label, feature name)
- The content never needs to be clicked or focused
Use a Popover when:
- The content includes links, buttons, inputs, or any interactive elements
- The content stays open while the user interacts with it
- The content is longer than 2 short lines
Rule of thumb: If a user needs to click something inside the overlay, use a Popover. If they only need to read something, use a Tooltip.
Conclusion
The shadcn tooltip component handles the most common contextual hint patterns in one composable API: Tooltip, TooltipTrigger, and TooltipContent. The 10 variants in this guide cover every production scenario you'll encounter: positioning, keyboard shortcuts, disabled buttons, delay control, rich content, icon buttons, avatars, form validation, custom styling, and programmatic control.
The two rules that will save you the most time: add <TooltipProvider> once at the root layout and never again inside individual components, and wrap disabled buttons in a span when you need a tooltip on them.
If you're building a SaaS dashboard, landing page, or admin panel with shadcn/ui, our step-by-step Next.js + shadcn/ui guide walks through the full project structure. Or skip straight to a finished starting point with a shadcn/ui landing page template — every template is built on Next.js App Router, Tailwind CSS, and TypeScript with the exact patterns shown in this guide.
Related Posts
Explore more shadcn component guides:
- Shadcn Accordion Component Guide — collapsible sections with smooth animations
- Shadcn Tabs Component Guide — tabbed interfaces for content organisation
- Shadcn Select Component Guide — custom dropdown selects with search
- Shadcn Avatar Component Guide — user avatars with fallback and group stacking
- Shadcn Resizable Sidebar Component Guide — dashboard layouts with drag-to-resize panels
- Shadcn Chat UI Guide — building chat interfaces where tooltips power hover actions
- shadcn/ui vs Material UI — comparing tooltip and component approaches across both libraries




