Tooltip
Tooltip uses TooltipWrapper as the recommended abstraction. It bundles trigger, content, optional provider, and arrow controls in one component while preserving Radix behavior.
Installation
1. Install dependencies:
npm install @radix-ui/react-tooltip2. Install the Tooltip primitive via shadcn:
npx shadcn@latest add tooltip3. Copy the wrapper source:
- Source in this monorepo:
packages/ui/src/common/overlays/tooltip-wrapper.tsx - Place in your project:
components/common/overlays/tooltip-wrapper.tsx - Required utility:
lib/utils.ts(forcnhelper) - Required primitive:
components/ui/tooltip.tsx
4. Update import paths to match your project structure.
Usage
import { TooltipWrapper as Tooltip } from "@hoag/ui/tooltip-wrapper";
import { Button } from "@hoag/ui/components/button";
export default function Demo() {
return (
<Tooltip
trigger={<Button variant="outline">Hover me</Button>}
content="Helpful tooltip text"
/>
);
}Default
<Tooltip
trigger={<Button variant="outline">Hover me</Button>}
content="Helpful tooltip text"
/>Placement and Arrow
<Tooltip
trigger={<Button size="sm" variant="outline">Top</Button>}
content="Top placement"
contentProps={{ side: "top" }}
/>
<Tooltip
trigger={<Button size="sm" variant="outline">No Arrow</Button>}
content="Arrow hidden"
showArrow={false}
contentProps={{ side: "bottom" }}
/>Trigger Modes
triggerAsChild (default)
Use when your trigger is already an interactive component like Button.
<Tooltip
trigger={<Button variant="secondary">Hover for details</Button>}
content="Uses your Button component as tooltip trigger"
/>Native trigger element (triggerAsChild={false})
Use when you want wrapper to render trigger directly.
<Tooltip
trigger="Native trigger"
triggerAsChild={false}
triggerClassName="inline-flex rounded-md border px-3 py-2 text-sm"
content="Rendered by TooltipTrigger"
/>Controlled Mode
Use controlled mode when tooltip state is managed by parent logic.
import { useState } from "react";
export default function ControlledTooltip() {
const [open, setOpen] = useState(false);
return (
<div className="flex items-center gap-3">
<Tooltip
open={open}
onOpenChange={setOpen}
trigger={<Button variant="outline">Controlled</Button>}
content={open ? "Tooltip is open" : "Tooltip is closed"}
/>
<Button size="sm" variant="ghost" onClick={() => setOpen((v) => !v)}>
Toggle programmatically
</Button>
</div>
);
}Provider Tuning
Use provider props to customize delay behavior globally per tooltip instance.
import { TooltipProvider } from "@hoag/ui/components/tooltip";
<Tooltip
trigger={<Button size="sm" variant="outline">Fast</Button>}
content="Short delay"
providerProps={{ delayDuration: 100, skipDelayDuration: 100 }}
/>
<TooltipProvider delayDuration={150} skipDelayDuration={120}>
<Tooltip
trigger={<Button size="sm" variant="outline">External Provider</Button>}
content="Uses parent provider"
withProvider={false}
/>
</TooltipProvider>If you set withProvider={false}, make sure a parent TooltipProvider exists.
API Reference
Props
| Prop | Type | Default | Description |
|---|---|---|---|
trigger | ReactNode | — | Tooltip trigger element |
triggerAsChild | boolean | true | Render trigger as child |
triggerClassName | string | — | Class override for trigger |
content | ReactNode | — | Tooltip content |
contentClassName | string | — | Class override for tooltip content |
contentProps | Omit<ComponentProps<typeof TooltipContent>, "children" | "className"> | — | Extra content props (e.g. side, align, sideOffset) |
showArrow | boolean | true | Show/hide arrow |
withProvider | boolean | true | Wrap with internal TooltipProvider |
providerProps | Omit<ComponentProps<typeof TooltipProvider>, "children"> | — | Provider props (e.g. delayDuration, skipDelayDuration) |
open | boolean | — | Controlled open state |
defaultOpen | boolean | false | Initial open state |
onOpenChange | (open: boolean) => void | — | Open state callback |
delayDuration | number | — | Delay before opening (Tooltip root prop) |
disableHoverableContent | boolean | — | Disable hoverable content behavior |
Content Positioning (contentProps)
Common props you can pass through contentProps:
side:"top" | "right" | "bottom" | "left"align:"start" | "center" | "end"sideOffset:numberalignOffset:number
Tips
- Keep
triggerAsChildenabled when using design-system buttons/links. - Use
providerProps.delayDurationto tune hover sensitivity for dense UIs. - Use
withProvider={false}if your layout already wraps a higher-levelTooltipProvider. - Prefer short, descriptive content to keep tooltips scannable and accessible.