Tooltip

An animated tooltip component that shows pop-up messages on hover.

Animated Tooltip

Loading...

Usage Guide

Install the clsx and tailwind-merge packages:

npm install clsx tw-merge framer-motion class-variance-authority

Next, add cn utility:

import { twMerge } from "tailwind-merge";
import { clsx, type ClassValue } from "clsx";

export function cn(...inputs: ClassValue[]) {
    return twMerge(clsx(inputs));
}

Copy and paste the following component source.

"use client";

import { cn } from "@/lib/utils";
import React, { useState } from "react";
import { motion, useMotionValue, useSpring, useTransform } from "framer-motion";

export interface AnimatedTooltipProps {
    children: React.ReactNode;
    label: string;
    onClick?: () => void;
    className?: string;
    delay?: number;
}

export function AnimatedTooltip({
    children,
    label,
    onClick,
    className,
    delay = 100,
    ...props
}: AnimatedTooltipProps) {
    const [isHovered, setIsHovered] = useState(false);

    const x = useMotionValue(0);
    const springConfig = { stiffness: 200, damping: 10 };
    const rotate = useSpring(useTransform(x, [-50, 50], [-25, 25]), springConfig);
    const translateX = useSpring(useTransform(x, [-50, 50], [-10, 10]), springConfig);

    let showTimeout: ReturnType<typeof setTimeout> | undefined = undefined;

    const handleMouseMove = (event: React.MouseEvent<HTMLDivElement>) => {
        const rect = event.currentTarget.getBoundingClientRect();
        const centerX = rect.left + rect.width / 2;
        x.set(event.clientX - centerX);
    };

    const handleMouseEnter = () => {
        showTimeout = setTimeout(() => setIsHovered(true), delay);
    };

    const handleMouseLeave = () => {
        setIsHovered(false);
        x.set(0);
        if (showTimeout) {
            clearTimeout(showTimeout);
        }
    };

    React.useEffect(() => {
        return () => {
            if (showTimeout) {
                clearTimeout(showTimeout);
            }
        };
    }, [showTimeout]);

    return (
        <motion.div
            onMouseEnter={handleMouseEnter}
            onMouseLeave={handleMouseLeave}
            onMouseMove={handleMouseMove}
            whileHover={{
                transition: { type: "spring", stiffness: 300, damping: 20 }
            }}
            whileTap={{ scale: 0.9 }}
            className={cn(
                " w-12 h-12 relative flex items-center justify-center rounded-full shadow-md cursor-pointer border bg-neutral-100 dark:bg-neutral-900 hover:shadow-lg",
                className
            )}
            onClick={onClick}
            {...props}
        >
            {children}
            {isHovered && (
                <motion.div
                    initial={{ opacity: 0, y: 10, scale: 0.75 }}
                    animate={{ opacity: 1, y: 0, scale: 1.2 }}
                    exit={{ opacity: 0, y: 10, scale: 0.75 }}
                    style={{
                        rotate,
                        translateX,
                        transformOrigin: "center center"
                    }}
                    className="absolute bottom-full mb-2 px-2 py-1 bg-black/75 text-white dark:bg-white dark:text-black text-xs rounded shadow-lg"
                >
                    {label}
                </motion.div>
            )}
        </motion.div>
    );
}

Props

Tooltip

PropTypeDescriptionDefault
childrenReactNodeThe content to be rendered inside this component_
delay?numberDelay in milliseconds before the tooltip appears or disappears.100
className?stringCustom class to apply to the tooltip._
onClick?() => voidCallback function to be called when the tooltip is clicked._