Components
Tooltip
Tooltip
Animated tooltip with customizable positioning, delay, and smooth transitions.
Installation
1
Install the packages
npm i motion clsx tailwind-merge2
Add util file
lib/util.ts
import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
3
Copy and paste the following code into your project
tooltip.tsx
"use client";
import { cn } from "@/lib/utils";
import { motion, AnimatePresence } from "motion/react";
import React, { useState, useRef } from "react";
interface TooltipProps {
content?: string;
children?: React.ReactNode;
position?: "top" | "bottom" | "left" | "right";
delay?: number;
}
const Tooltip: React.FC<TooltipProps> = ({
content = "This is a tooltip",
children,
position = "top",
delay = 300
}) => {
const [isVisible, setIsVisible] = useState(false);
const [timeoutId, setTimeoutId] = useState<NodeJS.Timeout | null>(null);
const tooltipRef = useRef<HTMLDivElement>(null);
const handleMouseEnter = () => {
const id = setTimeout(() => setIsVisible(true), delay);
setTimeoutId(id);
};
const handleMouseLeave = () => {
if (timeoutId) {
clearTimeout(timeoutId);
setTimeoutId(null);
}
setIsVisible(false);
};
const positionClasses = {
top: "bottom-full left-1/2 transform -translate-x-1/2 mb-2",
bottom: "top-full left-1/2 transform -translate-x-1/2 mt-2",
left: "right-full top-1/2 transform -translate-y-1/2 mr-2",
right: "left-full top-1/2 transform -translate-y-1/2 ml-2",
};
const arrowClasses = {
top: "top-full left-1/2 transform -translate-x-1/2 border-l-transparent border-r-transparent border-b-transparent",
bottom: "bottom-full left-1/2 transform -translate-x-1/2 border-l-transparent border-r-transparent border-t-transparent",
left: "left-full top-1/2 transform -translate-y-1/2 border-t-transparent border-b-transparent border-r-transparent",
right: "right-full top-1/2 transform -translate-y-1/2 border-t-transparent border-b-transparent border-l-transparent",
};
return (
<div
ref={tooltipRef}
className="relative inline-block"
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{children || (
<button className="px-4 py-2 bg-cyan-500 text-white rounded-lg hover:bg-cyan-600 transition-colors">
Hover me
</button>
)}
<AnimatePresence>
{isVisible && (
<motion.div
className={cn(
"absolute z-50 px-3 py-2",
"bg-neutral-900 dark:bg-neutral-700 text-white rounded-lg shadow-lg",
"border border-neutral-700 dark:border-neutral-600",
"max-w-xs text-sm text-center",
positionClasses[position]
)}
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.8 }}
transition={{ duration: 0.2, ease: "easeOut" }}
>
{content}
{/* Arrow */}
<div
className={cn(
"absolute w-0 h-0 border-4",
arrowClasses[position]
)}
style={{
borderColor: position === "top" ? "transparent transparent #374151 transparent" :
position === "bottom" ? "transparent transparent transparent #374151" :
position === "left" ? "transparent #374151 transparent transparent" :
"transparent transparent transparent #374151"
}}
/>
</motion.div>
)}
</AnimatePresence>
</div>
);
};
export default Tooltip;"use client";
import { cn } from "@/lib/utils";
import { motion, AnimatePresence } from "motion/react";
import React, { useState, useRef } from "react";
interface TooltipProps {
content?: string;
children?: React.ReactNode;
position?: "top" | "bottom" | "left" | "right";
delay?: number;
}
const Tooltip: React.FC<TooltipProps> = ({
content = "This is a tooltip",
children,
position = "top",
delay = 300
}) => {
const [isVisible, setIsVisible] = useState(false);
const [timeoutId, setTimeoutId] = useState<NodeJS.Timeout | null>(null);
const tooltipRef = useRef<HTMLDivElement>(null);
const handleMouseEnter = () => {
const id = setTimeout(() => setIsVisible(true), delay);
setTimeoutId(id);
};
const handleMouseLeave = () => {
if (timeoutId) {
clearTimeout(timeoutId);
setTimeoutId(null);
}
setIsVisible(false);
};
const positionClasses = {
top: "bottom-full left-1/2 transform -translate-x-1/2 mb-2",
bottom: "top-full left-1/2 transform -translate-x-1/2 mt-2",
left: "right-full top-1/2 transform -translate-y-1/2 mr-2",
right: "left-full top-1/2 transform -translate-y-1/2 ml-2",
};
const arrowClasses = {
top: "top-full left-1/2 transform -translate-x-1/2 border-l-transparent border-r-transparent border-b-transparent",
bottom: "bottom-full left-1/2 transform -translate-x-1/2 border-l-transparent border-r-transparent border-t-transparent",
left: "left-full top-1/2 transform -translate-y-1/2 border-t-transparent border-b-transparent border-r-transparent",
right: "right-full top-1/2 transform -translate-y-1/2 border-t-transparent border-b-transparent border-l-transparent",
};
return (
<div
ref={tooltipRef}
className="relative inline-block"
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{children || (
<button className="px-4 py-2 bg-cyan-500 text-white rounded-lg hover:bg-cyan-600 transition-colors">
Hover me
</button>
)}
<AnimatePresence>
{isVisible && (
<motion.div
className={cn(
"absolute z-50 px-3 py-2",
"bg-neutral-900 dark:bg-neutral-700 text-white rounded-lg shadow-lg",
"border border-neutral-700 dark:border-neutral-600",
"max-w-xs text-sm text-center",
positionClasses[position]
)}
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.8 }}
transition={{ duration: 0.2, ease: "easeOut" }}
>
{content}
{/* Arrow */}
<div
className={cn(
"absolute w-0 h-0 border-4",
arrowClasses[position]
)}
style={{
borderColor: position === "top" ? "transparent transparent #374151 transparent" :
position === "bottom" ? "transparent transparent transparent #374151" :
position === "left" ? "transparent #374151 transparent transparent" :
"transparent transparent transparent #374151"
}}
/>
</motion.div>
)}
</AnimatePresence>
</div>
);
};
export default Tooltip;4
Update the import paths to match your project setup
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| content | string | This is a tooltip | The text content of the tooltip. |
| children | ReactNode | null | The trigger element for the tooltip. |
| position | string | top | Position of the tooltip (top, bottom, left, right). |
| delay | number | 300 | Delay in milliseconds before showing the tooltip. |