Radix tooltip with a 500ms first-open delay, instant adjacent opens via skip-delay, 125ms origin-aware enter, near-instant fade exit.
npx shadcn@latest add @paragon/tooltip"use client";
import * as React from "react";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { cn } from "@/lib/utils";
/*
* Tooltip physics tuned by frequency: 500ms delay before the first open
* (never interrupt a scanning cursor), then TooltipProvider's
* skipDelayDuration lets adjacent tooltips open instantly. Enter is a
* quick 125ms origin-aware scale; exit is a near-instant 50ms fade —
* a leaving tooltip should not fight for attention.
*
* Wrap toolbars/regions in <TooltipProvider> so the skip-delay window
* is shared across neighboring tooltips.
*/
const tooltipStyles = `
@keyframes pg-tooltip-in { from { opacity: 0; scale: 0.97; } }
@keyframes pg-tooltip-out { to { opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
@keyframes pg-tooltip-in { from { opacity: 0; } }
}
`;
function TooltipProvider({
delayDuration = 500,
skipDelayDuration = 300,
...props
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
return (
<TooltipPrimitive.Provider
delayDuration={delayDuration}
skipDelayDuration={skipDelayDuration}
{...props}
/>
);
}
function Tooltip({
delayDuration = 500,
...props
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
return (
<TooltipPrimitive.Root
data-slot="tooltip"
delayDuration={delayDuration}
{...props}
/>
);
}
function TooltipTrigger(
props: React.ComponentProps<typeof TooltipPrimitive.Trigger>,
) {
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
}
function TooltipContent({
className,
sideOffset = 6,
...props
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
return (
<>
{/* Hoisted outside the Portal: React 19 keeps a hoistable <style> as a
child node, and the Radix Portal enforces a single child. */}
<style href="paragon-tooltip" precedence="paragon">{tooltipStyles}</style>
<TooltipPrimitive.Portal>
<TooltipPrimitive.Content
data-slot="tooltip-content"
sideOffset={sideOffset}
className={cn(
"z-50 origin-(--radix-tooltip-content-transform-origin) rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground",
"data-[state=open]:animate-[pg-tooltip-in_125ms_var(--ease-out)]",
"data-[state=closed]:animate-[pg-tooltip-out_50ms_var(--ease-exit)_forwards]",
className,
)}
{...props}
/>
</TooltipPrimitive.Portal>
</>
);
}
export { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent };