Magnify Dock

App dock where icons grow smoothly as the cursor approaches.

Installation

  1. Install the dependencies

    $ npm i motion lucide-react
  2. Copy the source into components/ui/magnify-dock.tsx

    components/ui/magnify-dock.tsx
    "use client";
    
    import { motion, useMotionValue, useSpring, useTransform, type MotionValue } from "motion/react";
    import { Calendar, Camera, Folder, Mail, MessageCircle, Music, Settings } from "lucide-react";
    import { useRef, type ComponentType } from "react";
    
    type DockItem = { label: string; icon: ComponentType<{ className?: string }>; color: string };
    
    const items: DockItem[] = [
      { label: "Finder", icon: Folder, color: "from-sky-400 to-blue-600" },
      { label: "Mail", icon: Mail, color: "from-blue-400 to-indigo-600" },
      { label: "Messages", icon: MessageCircle, color: "from-emerald-400 to-green-600" },
      { label: "Calendar", icon: Calendar, color: "from-rose-400 to-red-600" },
      { label: "Photos", icon: Camera, color: "from-amber-400 to-orange-600" },
      { label: "Music", icon: Music, color: "from-pink-400 to-fuchsia-600" },
      { label: "Settings", icon: Settings, color: "from-zinc-400 to-zinc-600" },
    ];
    
    function DockIcon({ item, mouseX }: { item: DockItem; mouseX: MotionValue<number> }) {
      const ref = useRef<HTMLButtonElement>(null);
      const distance = useTransform(mouseX, (x) => {
        const rect = ref.current?.getBoundingClientRect();
        if (!rect) return Infinity;
        return x - (rect.left + rect.width / 2);
      });
      const size = useSpring(useTransform(distance, [-140, 0, 140], [44, 72, 44]), { stiffness: 300, damping: 22, mass: 0.2 });
      const Icon = item.icon;
    
      return (
        <motion.button
          ref={ref}
          aria-label={item.label}
          style={{ width: size, height: size }}
          whileTap={{ scale: 0.9 }}
          className={`group relative grid shrink-0 place-items-center rounded-2xl bg-gradient-to-br text-white shadow-md ${item.color}`}
        >
          <Icon className="size-1/2" />
          <span className="pointer-events-none absolute -top-9 whitespace-nowrap rounded-md bg-zinc-900 px-2 py-1 text-xs text-white opacity-0 transition-opacity group-hover:opacity-100">
            {item.label}
          </span>
        </motion.button>
      );
    }
    
    export function MagnifyDock({ apps = items }: { apps?: DockItem[] }) {
      const mouseX = useMotionValue(Infinity);
      return (
        <div
          onMouseMove={(e) => mouseX.set(e.clientX)}
          onMouseLeave={() => mouseX.set(Infinity)}
          className="flex h-[88px] items-end gap-3 rounded-3xl border border-zinc-200 bg-white/80 px-4 pb-3 shadow-xl shadow-zinc-900/10 backdrop-blur-md"
        >
          {apps.map((item) => (
            <DockIcon key={item.label} item={item} mouseX={mouseX} />
          ))}
        </div>
      );
    }
    
    export default function Demo() {
      return (
        <div className="grid place-items-center pt-10">
          <MagnifyDock />
        </div>
      );
    }
    
  3. Use it

    The file exports the component and a Demo showing typical usage. Import whichever you need.