tooltip
A customizable and accessible tooltip component with Floating UI positioning, state-aware styling hooks, and flexible triggers.
Features
- Floating UI powered positioning - Smart placement with
flip,shift, andoffsetmiddleware. - State-aware styling hooks -
data-stateanddata-sideattributes for state and placement styling. - Transform-origin variable - Uses
--gentleduck-tooltip-content-transform-originfor direction-aware animations. - Customizable delays - Configure open delays globally with
TooltipProvideror per tooltip withdelayDuration. - Flexible triggers - Wrap any element using
asChild. - Accessible by default - Implements proper ARIA attributes and keyboard navigation.
- Portal rendering - Renders tooltips in a portal to avoid layout clipping.
Philosophy
Tooltips are the lightest touch of contextual help — they appear on hover, require no interaction, and disappear when attention moves on. We build on Floating UI because positioning against viewport edges, scroll containers, and dynamic layouts is harder than it looks. The data-state and data-side attributes give you animation hooks without JavaScript state management.
How It's Built
Installation
npx @gentleduck/cli add tooltip
npx @gentleduck/cli add tooltip
Usage
import { Tooltip, TooltipContent, TooltipTrigger, TooltipProvider } from '@/components/ui/tooltip'
// app/layout.tsx (once)
<TooltipProvider>{children}</TooltipProvider>
<Tooltip delayDuration={500}>
<TooltipTrigger>Hover</TooltipTrigger>
<TooltipContent>Tooltip text</TooltipContent>
</Tooltip>import { Tooltip, TooltipContent, TooltipTrigger, TooltipProvider } from '@/components/ui/tooltip'
// app/layout.tsx (once)
<TooltipProvider>{children}</TooltipProvider>
<Tooltip delayDuration={500}>
<TooltipTrigger>Hover</TooltipTrigger>
<TooltipContent>Tooltip text</TooltipContent>
</Tooltip>Examples
Basic
Custom Trigger with asChild
<Tooltip>
<TooltipTrigger asChild>
<span className="cursor-pointer underline">Hover me</span>
</TooltipTrigger>
<TooltipContent>Custom element trigger</TooltipContent>
</Tooltip><Tooltip>
<TooltipTrigger asChild>
<span className="cursor-pointer underline">Hover me</span>
</TooltipTrigger>
<TooltipContent>Custom element trigger</TooltipContent>
</Tooltip>Animated Tooltip
<Tooltip>
<TooltipTrigger>Hover</TooltipTrigger>
<TooltipContent className="TooltipContent">Animated tooltip</TooltipContent>
</Tooltip><Tooltip>
<TooltipTrigger>Hover</TooltipTrigger>
<TooltipContent className="TooltipContent">Animated tooltip</TooltipContent>
</Tooltip>.TooltipContent {
transform-origin: var(--gentleduck-tooltip-content-transform-origin);
transition: transform 150ms ease, opacity 150ms ease;
}
.TooltipContent[data-state='closed'] {
opacity: 0;
transform: scale(0.95);
}
.TooltipContent[data-state='delayed-open'],
.TooltipContent[data-state='instant-open'] {
opacity: 1;
transform: scale(1);
}.TooltipContent {
transform-origin: var(--gentleduck-tooltip-content-transform-origin);
transition: transform 150ms ease, opacity 150ms ease;
}
.TooltipContent[data-state='closed'] {
opacity: 0;
transform: scale(0.95);
}
.TooltipContent[data-state='delayed-open'],
.TooltipContent[data-state='instant-open'] {
opacity: 1;
transform: scale(1);
}Styling Hooks
data-state- Set on trigger and content (closed,delayed-open,instant-open) for state-based styling.data-side- Set on content (top,right,bottom,left) for placement-aware styles.--gentleduck-tooltip-content-transform-origin- CSS variable for animation transform origin.
API Reference
TooltipProvider
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | -- | Tooltip tree to provide behavior for |
delayDuration | number | 700 | Delay before opening tooltips |
skipDelayDuration | number | 300 | Window where moving between triggers skips delay |
disableHoverableContent | boolean | false | Close tooltip as soon as pointer leaves trigger |
Tooltip
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | -- | Tooltip sub-components (TooltipTrigger, TooltipContent) |
open | boolean | -- | Controlled open state |
defaultOpen | boolean | false | Uncontrolled initial open state |
onOpenChange | (open: boolean) => void | -- | Callback when open state changes |
delayDuration | number | 700 | Per-tooltip delay override |
disableHoverableContent | boolean | false | Per-tooltip hover-content behavior override |
dir | 'ltr' | 'rtl' | -- | Text direction. Resolved by primitives useDirection (dir prop -> DirectionProvider -> 'ltr'). |
...props | React.ComponentPropsWithRef<typeof TooltipPrimitive.Root> | -- | Additional root props |
TooltipTrigger
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Renders the child element as the trigger instead of a button |
children | React.ReactNode | -- | Content rendered inside the trigger |
className | string | -- | Additional CSS class names to apply |
...props | Omit<React.ComponentPropsWithRef<typeof TooltipPrimitive.Trigger>, 'size'> | -- | Additional props to spread to the button element |
TooltipContent
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | -- | Additional CSS class names to apply |
children | React.ReactNode | -- | Content rendered inside the tooltip |
ref | React.Ref<HTMLDivElement> | -- | Ref forwarded to the content container |
forceMount | boolean | -- | Keep content mounted for external animation control |
side | 'top' | 'right' | 'bottom' | 'left' | 'top' | Preferred side relative to the trigger |
align | 'start' | 'center' | 'end' | 'center' | Alignment on the chosen side |
sideOffset | number | 4 | Main-axis offset from trigger |
alignOffset | number | 0 | Cross-axis offset from trigger |
...props | React.ComponentPropsWithRef<typeof TooltipPrimitive.Content> | -- | Additional props to spread to the content div |
RTL Support
Set dir="rtl" on Tooltip for a local override, or set DirectionProvider once at app/root level for global direction. This mirrors tooltip positioning in right-to-left layouts.
<TooltipProvider>
<Tooltip dir="rtl">
<TooltipTrigger>مرر الماوس</TooltipTrigger>
<TooltipContent>نص التلميح</TooltipContent>
</Tooltip>
</TooltipProvider><TooltipProvider>
<Tooltip dir="rtl">
<TooltipTrigger>مرر الماوس</TooltipTrigger>
<TooltipContent>نص التلميح</TooltipContent>
</Tooltip>
</TooltipProvider>