Skip to content

Magnetic Nav Dock

A macOS-style dock where icons magnify based on real distance from the cursor.

Freeplayfulglass
Category
Components
Added
2026-08-22
Deps
1
Updated
2026-09-01
Preview
🏠
🔍
💬
📁
⚙️

Props

44
72
Requires framer-motion
"use client";

import { useRef } from "react";
import { motion, useMotionValue, useSpring, useTransform, type MotionValue } from "framer-motion";

function DockIcon({
  icon,
  mouseX,
  baseSize,
  maxSize,
}: {
  icon: string;
  mouseX: MotionValue<number>;
  baseSize: number;
  maxSize: number;
}) {
  const ref = useRef<HTMLDivElement>(null);

  const distance = useTransform(mouseX, (x) => {
    const el = ref.current;
    if (!el) return Infinity;
    const rect = el.getBoundingClientRect();
    return x - (rect.left + rect.width / 2);
  });

  const sizeRaw = useTransform(distance, [-120, 0, 120], [baseSize, maxSize, baseSize]);
  const size = useSpring(sizeRaw, { mass: 0.2, stiffness: 250, damping: 18 });

  return (
    <motion.div
      ref={ref}
      style={{ width: size, height: size }}
      className="flex items-center justify-center rounded-2xl bg-white text-2xl shadow-md"
    >
      {icon}
    </motion.div>
  );
}

export function MagneticNavDock({
  icons = ["🏠", "🔍", "💬", "📁", "⚙️"],
  baseSize = 44,
  maxSize = 72,
}: {
  icons?: string[];
  baseSize?: number;
  maxSize?: number;
}) {
  const mouseX = useMotionValue(Infinity);

  return (
    <div
      onMouseMove={(e) => mouseX.set(e.clientX)}
      onMouseLeave={() => mouseX.set(Infinity)}
      className="flex items-end gap-3 rounded-3xl border border-neutral-800 bg-neutral-900/60 px-4 py-3"
    >
      {icons.map((icon, i) => (
        <DockIcon key={i} icon={icon} mouseX={mouseX} baseSize={baseSize} maxSize={maxSize} />
      ))}
    </div>
  );
}
Notes

About this component

This is the macOS dock trick done properly: instead of each icon watching its own hover state, one shared mouseX motion value lives at the container level, and every icon computes its own live distance from it via useTransform reading its own DOM center through a ref — proximity, not a boolean, drives the size, so neighbors ease into the magnification too. The mouseX value resets to Infinity on mouse-leave rather than some other sentinel, which is the trick worth stealing elsewhere: every icon's distance calculation naturally becomes enormous and every size clamps back to baseSize with no separate "is anyone hovering" branch needed. Reach for this over Magnetic Button's simpler pull-toward-cursor trick when a whole row of elements should feel like one continuous surface reacting to the pointer, not one isolated target. One thing to watch: the spring output feeds a width/height style, a layout-triggering property rather than a compositor-only transform — fine for five icons, worth switching to a scale transform if this pattern gets stretched across a much longer row.

Curator’s note

One shared mouseX MotionValue at the container, distance computed per-icon — that's the whole trick. Chasing this with per-icon onMouseEnter/onMouseLeave gets you a binary hover state, not the continuous falloff that makes a dock feel magnetic.

Related

Pairs well with

Matched on shared tags and category — the entries most likely to be used alongside this one.

Featured
New

Snapping Cursor

components

A cursor dot that latches onto interactive elements and takes their exact shape.

cursormagneticsnap

Tilt Card

components

A 3D card that tilts toward the cursor with spring physics.

card3dcursor
Featured

Magnetic Button

components

A button whose label leans toward the cursor with a springy pull.

buttoncursorspring