← Back
Micro-interaction

Magnetic Button

Buttons shift toward the cursor with a magnetic pull, then spring back smoothly on mouse leave.

move cursor slowly near each button

MagneticButton.tsx71 lines
"use client";

import { useRef, useState } from "react";

interface MagBtnProps {
  children: React.ReactNode;
  variant?: "filled" | "outline" | "ghost";
  strength?: number;
}

function MagneticBtn({ children, variant = "filled", strength = 0.38 }: MagBtnProps) {
  const ref = useRef<HTMLButtonElement>(null);
  const [offset, setOffset] = useState({ x: 0, y: 0 });
  const isResting = offset.x === 0 && offset.y === 0;

  function onMouseMove(e: React.MouseEvent) {
    const el = ref.current;
    if (!el) return;
    const { left, top, width, height } = el.getBoundingClientRect();
    setOffset({
      x: (e.clientX - (left + width / 2)) * strength,
      y: (e.clientY - (top + height / 2)) * strength,
    });
  }

  function onMouseLeave() {
    setOffset({ x: 0, y: 0 });
  }

  const variantClass = {
    filled:
      "bg-zinc-900 dark:bg-zinc-50 text-white dark:text-zinc-900",
    outline:
      "border-2 border-zinc-900 dark:border-zinc-50 text-zinc-900 dark:text-zinc-50",
    ghost:
      "text-zinc-900 dark:text-zinc-50 bg-zinc-100 dark:bg-zinc-800",
  }[variant];

  return (
    <button
      ref={ref}
      onMouseMove={onMouseMove}
      onMouseLeave={onMouseLeave}
      style={{
        transform: `translate(${offset.x}px, ${offset.y}px)`,
        transition: isResting
          ? "transform 500ms cubic-bezier(0.23, 1, 0.32, 1)"
          : "transform 80ms ease-out",
      }}
      className={`px-8 py-4 rounded-full font-semibold text-sm tracking-wide select-none cursor-pointer ${variantClass}`}
    >
      {children}
    </button>
  );
}

export default function MagneticButton() {
  return (
    <div className="flex flex-col items-center gap-12 py-12 w-full">
      <div className="flex flex-wrap gap-10 items-center justify-center">
        <MagneticBtn variant="filled">Get Started</MagneticBtn>
        <MagneticBtn variant="outline" strength={0.45}>Learn More</MagneticBtn>
        <MagneticBtn variant="ghost" strength={0.5}>Explore</MagneticBtn>
      </div>
      <p className="text-xs text-zinc-400 dark:text-zinc-600 font-mono">
        move cursor slowly near each button
      </p>
    </div>
  );
}

More in Micro-interaction