Magnetic Button
Button that is pulled toward the cursor with spring physics.
components/ui/magnetic-button.tsx
"use client";
import { motion, useMotionValue, useSpring, type HTMLMotionProps } from "motion/react";
import { Sparkles } from "lucide-react";
import { useRef } from "react";
type MagneticButtonProps = HTMLMotionProps<"button"> & {
/** How far the button follows the cursor (0 - 1) */
strength?: number;
};
export function MagneticButton({ children, strength = 0.35, className = "", ...props }: MagneticButtonProps) {
const ref = useRef<HTMLButtonElement>(null);
const x = useMotionValue(0);
const y = useMotionValue(0);
const springX = useSpring(x, { stiffness: 220, damping: 16, mass: 0.4 });
const springY = useSpring(y, { stiffness: 220, damping: 16, mass: 0.4 });
function handleMove(e: React.MouseEvent<HTMLButtonElement>) {
const rect = ref.current?.getBoundingClientRect();
if (!rect) return;
x.set((e.clientX - (rect.left + rect.width / 2)) * strength);
y.set((e.clientY - (rect.top + rect.height / 2)) * strength);
}
function reset() {
x.set(0);
y.set(0);
}
return (
<motion.button
ref={ref}
onMouseMove={handleMove}
onMouseLeave={reset}
style={{ x: springX, y: springY }}
whileTap={{ scale: 0.96 }}
className={`inline-flex items-center gap-2 rounded-full border border-zinc-200 bg-white px-7 py-3.5 text-sm font-medium text-zinc-900 shadow-sm transition-shadow hover:shadow-md ${className}`}
{...props}
>
{children}
</motion.button>
);
}
export default function Demo() {
return (
<div className="grid place-items-center p-10">
<MagneticButton>
<Sparkles className="size-4 text-blue-600" />
Hover me
</MagneticButton>
</div>
);
}
Installation
Install the dependencies
$ npm i motion lucide-reactCopy the source into
components/ui/magnetic-button.tsxcomponents/ui/magnetic-button.tsx"use client"; import { motion, useMotionValue, useSpring, type HTMLMotionProps } from "motion/react"; import { Sparkles } from "lucide-react"; import { useRef } from "react"; type MagneticButtonProps = HTMLMotionProps<"button"> & { /** How far the button follows the cursor (0 - 1) */ strength?: number; }; export function MagneticButton({ children, strength = 0.35, className = "", ...props }: MagneticButtonProps) { const ref = useRef<HTMLButtonElement>(null); const x = useMotionValue(0); const y = useMotionValue(0); const springX = useSpring(x, { stiffness: 220, damping: 16, mass: 0.4 }); const springY = useSpring(y, { stiffness: 220, damping: 16, mass: 0.4 }); function handleMove(e: React.MouseEvent<HTMLButtonElement>) { const rect = ref.current?.getBoundingClientRect(); if (!rect) return; x.set((e.clientX - (rect.left + rect.width / 2)) * strength); y.set((e.clientY - (rect.top + rect.height / 2)) * strength); } function reset() { x.set(0); y.set(0); } return ( <motion.button ref={ref} onMouseMove={handleMove} onMouseLeave={reset} style={{ x: springX, y: springY }} whileTap={{ scale: 0.96 }} className={`inline-flex items-center gap-2 rounded-full border border-zinc-200 bg-white px-7 py-3.5 text-sm font-medium text-zinc-900 shadow-sm transition-shadow hover:shadow-md ${className}`} {...props} > {children} </motion.button> ); } export default function Demo() { return ( <div className="grid place-items-center p-10"> <MagneticButton> <Sparkles className="size-4 text-blue-600" /> Hover me </MagneticButton> </div> ); }Use it
The file exports the component and a
Demoshowing typical usage. Import whichever you need.