Skip to content

Magnetic Button

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

Freeplayfuleditorial
Category
Components
Added
2026-08-01
Deps
1
Updated
2026-09-01
Preview

Props

24
Rounded
Requires framer-motion
"use client";

import { useRef, useState } from "react";
import { motion } from "framer-motion";

export function MagneticButton({
  label = "Copy this",
  strength = 24,
  rounded = true,
  variant = "red",
}: {
  label?: string;
  strength?: number;
  rounded?: boolean;
  variant?: "red" | "outline" | "dark";
}) {
  const ref = useRef<HTMLButtonElement>(null);
  const [pos, setPos] = useState({ x: 0, y: 0 });

  function handleMove(e: React.MouseEvent<HTMLButtonElement>) {
    const el = ref.current;
    if (!el) return;
    const rect = el.getBoundingClientRect();
    const x = ((e.clientX - rect.left) / rect.width - 0.5) * strength;
    const y = ((e.clientY - rect.top) / rect.height - 0.5) * strength;
    setPos({ x, y });
  }

  const variantClasses =
    variant === "red"
      ? "bg-red-500 text-white"
      : variant === "outline"
        ? "border border-neutral-900 bg-transparent text-neutral-900"
        : "bg-neutral-900 text-white";

  return (
    <button
      ref={ref}
      onMouseMove={handleMove}
      onMouseLeave={() => setPos({ x: 0, y: 0 })}
      className={
        "relative px-8 py-4 font-semibold tracking-tight transition-colors " +
        variantClasses +
        (rounded ? " rounded-full" : " rounded-none")
      }
    >
      <motion.span
        animate={{ x: pos.x, y: pos.y }}
        transition={{ type: "spring", stiffness: 150, damping: 12, mass: 0.4 }}
        className="block"
      >
        {label}
      </motion.span>
    </button>
  );
}
Notes

About this component

The pull toward the cursor turns a static pill button into something that feels alive under the mouse, computed from one measurement per mousemove: cursor position relative to the button's own bounding rect, remapped to a small offset, applied with a spring to the button's inner label — not the button itself. That's the detail worth noticing: only the motion.span moves, so the button's actual hit-box stays exactly where the eye expects it, and a click mid-drift still lands correctly. Reach for it over a plain CSS hover-scale on a small number of hero-level, high-intent CTAs; this library's Magnetic Nav Dock applies the same underlying idea to a whole row of icons instead of one label. The cost to know about: position tracking here goes through plain useState on every mousemove event, re-rendering the component on every pixel of movement — the dock and this library's cursor-follow blob both route raw pointer position through a Framer motion value instead specifically to avoid that, making this the more expensive of the two mouse-tracking patterns in the library, fine for one button, worth reconsidering for many.

Curator’s note

Curated & rebuilt in-house for viberdy — tuned spring values for a snappier feel than the common tutorial version.

Related

Pairs well with

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

Featured
New

A contextual chip that trails the pointer and names whatever it is over.

cursorlabelspring
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