Tooltip - Accessible Tooltip Component

A Radix-based tooltip with dark and white variants, arrow pointer, and configurable placement sides. Perfect for icon labels, dock items, and contextual help.

Installation

npx shadcn@latest add "https://ui-struct.vercel.app/r/tooltip"

Usage

import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<button>Hover me</button>
</TooltipTrigger>
<TooltipContent>Tooltip text</TooltipContent>
</Tooltip>
</TooltipProvider>

Preview

Hover the button to see the default dark tooltip with arrow.

Variants

Use variant="dark" (default) or variant="white".

Variant Options

VariantDescription
darkBlack background with light text (default)
whiteWhite background with dark text

White Variant

Ideal on dark surfaces — matches docks and dark UI chrome.

Placement Sides

Control where the tooltip appears with the side prop.

Side Options

SideDescription
topAbove the trigger (default)
rightTo the right of the trigger
bottomBelow the trigger
leftTo the left of the trigger

Delay

Adjust open delay via TooltipProvider delayDuration.

TooltipContent Props

PropTypeDefaultDescription
variant'dark' | 'white''dark'Color style for content and arrow
side'top' | 'right' | 'bottom' | 'left''top'Placement relative to trigger
sideOffsetnumber8Distance from the trigger in pixels
classNamestring-Additional CSS classes
childrenReactNode-Tooltip content

Features

  • Arrow — Built-in pointer that matches the tooltip color
  • Variants — Dark and white themes
  • Accessible — Powered by Radix UI
  • Animated — Fade and zoom enter/exit transitions
  • Flexible — Works with any trigger via asChild

Full Code

'use client';
import * as React from 'react';
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
import { cn } from '@/lib/utils';
const TooltipProvider = TooltipPrimitive.Provider;
const Tooltip = TooltipPrimitive.Root;
const TooltipTrigger = TooltipPrimitive.Trigger;
const TooltipPortal = TooltipPrimitive.Portal;
const tooltipVariants = {
dark: {
content: 'bg-neutral-900 text-neutral-50',
arrow: 'fill-neutral-900',
},
white: {
content: 'bg-white text-neutral-900',
arrow: 'fill-white',
},
} as const;
type TooltipVariant = keyof typeof tooltipVariants;
type TooltipContentProps = React.ComponentPropsWithoutRef<
typeof TooltipPrimitive.Content
> & {
variant?: TooltipVariant;
};
const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
TooltipContentProps
>(
(
{
className,
sideOffset = 8,
side = 'top',
variant = 'dark',
children,
...props
},
ref
) => (
<TooltipPortal>
<TooltipPrimitive.Content
ref={ref}
side={side}
sideOffset={sideOffset}
className={cn(
'z-[100] rounded-md px-3 py-1.5 text-xs shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
tooltipVariants[variant].content,
className
)}
{...props}
>
{children}
<TooltipPrimitive.Arrow className={tooltipVariants[variant].arrow} />
</TooltipPrimitive.Content>
</TooltipPortal>
)
);
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
// @docs-preview-start
export default function TooltipPreview() {
return (
<TooltipProvider delayDuration={100}>
<div className="flex h-[160px] w-full items-center justify-center">
<Tooltip>
<TooltipTrigger asChild>
<button
type="button"
className="rounded-lg bg-neutral-900 px-4 py-2 text-sm font-medium text-white dark:bg-white dark:text-neutral-900"
>
Hover me
</button>
</TooltipTrigger>
<TooltipContent>Default dark tooltip</TooltipContent>
</Tooltip>
</div>
</TooltipProvider>
);
}
// @docs-preview-end