Launched best Saas Marketing template at cheap — Check out

Interactive Tooltip

A dynamic tooltip component with mouse-following animations and spring physics. Perfect for displaying user profiles or team member information with engaging hover effects.

Installation

npm install motion react
pnpm install motion react
yarn add motion react
bun add motion react
'use client';
import {
  AnimatePresence,
  motion,
  useMotionValue,
  useSpring,
  useTransform,
} from 'motion/react';
import { useState } from 'react';

const defaultItems = [
  {
    id: 1,
    name: 'Alex Rivera',
    designation: 'Lead Developer',
    image:
      'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=100&h=100&fit=crop&crop=face',
  },
  {
    id: 2,
    name: 'Erick Williams',
    designation: 'UI/UX Designer',
    image:
      'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=100&h=100&fit=crop&crop=face',
  },
  {
    id: 3,
    name: 'Mike Johnson',
    designation: 'Product Manager',
    image:
      'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100&h=100&fit=crop&crop=face',
  },
  {
    id: 4,
    name: 'Emma Davis',
    designation: 'Marketing Lead',
    image:
      'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=100&h=100&fit=crop&crop=face',
  },
];

export default function InteractiveTooltip({
  items = defaultItems,
}: {
  items?: {
    id: number;
    name: string;
    designation: string;
    image: string;
  }[];
}) {
  const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
  const springConfig = { stiffness: 100, damping: 5 };
  const x = useMotionValue(0); // going to set this value on mouse move
  // rotate the tooltip
  const rotate = useSpring(
    useTransform(x, [-100, 100], [-45, 45]),
    springConfig,
  );
  // translate the tooltip
  const translateX = useSpring(
    useTransform(x, [-100, 100], [-50, 50]),
    springConfig,
  );
  const handleMouseMove = (event: any) => {
    const halfWidth = event.target.offsetWidth / 2;
    x.set(event.nativeEvent.offsetX - halfWidth); // set the x value, which is then used in transform and rotate
  };

  return (
    <>
      {items.map((item, idx) => (
        <div
          className="group relative -mr-4"
          key={item.name}
          onMouseEnter={() => setHoveredIndex(item.id)}
          onMouseLeave={() => setHoveredIndex(null)}
        >
          <AnimatePresence mode="popLayout">
            {hoveredIndex === item.id && (
              <motion.div
                initial={{ opacity: 0, y: 20, scale: 0.6 }}
                animate={{
                  opacity: 1,
                  y: 0,
                  scale: 1,
                  transition: {
                    type: 'spring',
                    stiffness: 260,
                    damping: 10,
                  },
                }}
                exit={{ opacity: 0, y: 20, scale: 0.6 }}
                style={{
                  translateX: translateX,
                  rotate: rotate,
                  whiteSpace: 'nowrap',
                }}
                className="bg-background/95 border-border absolute -top-16 left-1/2 z-50 flex -translate-x-1/2 flex-col items-center justify-center rounded-lg border px-4 py-2 text-xs shadow-xl backdrop-blur-sm"
              >
                <div className="from-primary/5 to-primary/5 absolute inset-0 rounded-lg bg-gradient-to-b via-transparent" />
                <div className="via-primary absolute inset-x-10 -bottom-px z-30 h-px w-[20%] bg-gradient-to-r from-transparent to-transparent" />
                <div className="via-primary/60 absolute -bottom-px left-10 z-30 h-px w-[40%] bg-gradient-to-r from-transparent to-transparent" />
                <div className="text-foreground relative z-30 text-base font-bold">
                  {item.name}
                </div>
                <div className="text-muted-foreground text-xs">
                  {item.designation}
                </div>
              </motion.div>
            )}
          </AnimatePresence>
          <img
            onMouseMove={handleMouseMove}
            height={100}
            width={100}
            src={item.image}
            alt={item.name}
            className="border-background ring-primary/20 group-hover:ring-primary/40 relative !m-0 h-14 w-14 rounded-full border-2 object-cover object-top !p-0 ring-2 transition duration-500 group-hover:z-30 group-hover:scale-105"
          />
        </div>
      ))}
    </>
  );
}

Usage

import InteractiveTooltip from "@/components/mvpblocks/creative/interactive-tooltip";

const teamMembers = [
  {
    id: 1,
    name: "John Doe",
    designation: "Senior Developer",
    image: "https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=100&h=100&fit=crop&crop=face"
  },
  {
    id: 2,
    name: "Jane Smith",
    designation: "UI/UX Designer",
    image: "https://images.unsplash.com/photo-1494790108755-2616b612b786?w=100&h=100&fit=crop&crop=face"
  },
  {
    id: 3,
    name: "Mike Johnson",
    designation: "Product Manager",
    image: "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100&h=100&fit=crop&crop=face"
  }
];

export default function MyComponent() {
  return (
    <div className="flex items-center justify-center min-h-[200px] p-8">
      <div className="flex items-center">
        <InteractiveTooltip items={teamMembers} />
      </div>
    </div>
  );
}

API

PropTypeDefault
items?
Array<{id: number, name: string, designation: string, image: string}>
[]

Item Properties

PropTypeDefault
id?
number
-
name?
string
-
designation?
string
-
image?
string
-

Features

  • 🎯 Mouse Following: Tooltip follows mouse movement with smooth spring physics
  • 🔄 Dynamic Rotation: Tooltip rotates based on mouse position for natural feel
  • Spring Animations: Smooth enter/exit animations with configurable spring physics
  • 🎨 Gradient Effects: Beautiful gradient borders for visual appeal
  • 📱 Responsive Design: Works seamlessly on different screen sizes
  • 🖼️ Image Hover Effects: Scale and z-index changes on hover
  • 🎪 Stacked Layout: Overlapping avatars with proper z-index management

Examples

Development Team

const devTeam = [
  {
    id: 1,
    name: "Alex Rivera",
    designation: "Lead Frontend Developer",
    image: "/team/alex.jpg"
  },
  {
    id: 2,
    name: "Sarah Chen",
    designation: "Backend Engineer",
    image: "/team/sarah.jpg"
  },
  {
    id: 3,
    name: "Marcus Williams",
    designation: "DevOps Engineer",
    image: "/team/marcus.jpg"
  }
];

<InteractiveTooltip items={devTeam} />

Customer Success Team

const customerTeam = [
  {
    id: 1,
    name: "Emma Thompson",
    designation: "Customer Success Manager",
    image: "/team/emma.jpg"
  },
  {
    id: 2,
    name: "David Park",
    designation: "Support Specialist",
    image: "/team/david.jpg"
  },
  {
    id: 3,
    name: "Lisa Rodriguez",
    designation: "Account Manager",
    image: "/team/lisa.jpg"
  }
];

<InteractiveTooltip items={customerTeam} />

Executive Team

const executives = [
  {
    id: 1,
    name: "Michael Johnson",
    designation: "Chief Executive Officer",
    image: "/executives/ceo.jpg"
  },
  {
    id: 2,
    name: "Jennifer Davis",
    designation: "Chief Technology Officer",
    image: "/executives/cto.jpg"
  },
  {
    id: 3,
    name: "Robert Kim",
    designation: "Chief Marketing Officer",
    image: "/executives/cmo.jpg"
  }
];

<InteractiveTooltip items={executives} />

Animation Details

Spring Configuration

The component uses carefully tuned spring physics:

  • Stiffness: 100 (smooth but responsive)
  • Damping: 5 (minimal bouncing)
  • Rotation Range: -45° to +45° based on mouse position
  • Translation Range: -50px to +50px horizontal offset

Hover Effects

  • Scale: Images scale to 105% on hover
  • Z-Index: Hovered image moves to front layer (z-30)
  • Transition: 500ms duration for smooth scaling

Tooltip Animation

  • Enter: Opacity 0→1, Y 20→0, Scale 0.6→1
  • Exit: Opacity 1→0, Y 0→20, Scale 1→0.6
  • Spring Type: High stiffness (260) with controlled damping (10)

Styling Customization

The component uses Tailwind classes and can be customized:

Avatar Styling

.avatar-custom {
  @apply h-16 w-16 border-2 border-background rounded-full ring-2 ring-primary/20;
}

Tooltip Styling

.tooltip-custom {
  @apply bg-background/95 backdrop-blur-sm border border-border text-foreground px-4 py-2 rounded-lg shadow-xl;
}

Theme Colors

The component uses semantic theme colors for consistency:

  • Background: bg-background/95 with blur for modern glass effect
  • Border: border-border for subtle outlines
  • Text: text-foreground for primary text, text-muted-foreground for secondary
  • Ring Effect: ring-primary/20 with hover state ring-primary/40
  • Gradients: from-primary/5 via-transparent to-primary/5 for subtle depth

Gradient Effects

The tooltip includes multiple gradient layers using theme colors:

  • Background gradient: Subtle vertical gradient with primary/5
  • Primary gradient: 20% width, via-primary
  • Secondary gradient: 40% width, via-primary/60

Best Practices

  1. Image Optimization: Use optimized images (WebP format recommended)
  2. Responsive Images: Provide appropriate sizes for different screen densities
  3. Accessibility: Include proper alt text for profile images
  4. Performance: Limit the number of items to avoid performance issues
  5. Spacing: Allow adequate spacing between avatars for comfortable interaction