·2 min read
magnetic button effect.
An interactive button that follows the cursor with a magnetic pull effect — built with React and Motion for buttery smooth spring physics.
reactmotioninteraction
Magnetic Button Effect
Buttons that subtly follow your cursor create a delightful, almost physical interaction. The key is spring physics — not linear interpolation.
The Concept
When the cursor enters the button's bounding box, the button shifts toward the cursor position with a spring animation. On leave, it snaps back to center.
tsx
import { useRef, useState } from "react";
import { motion, useSpring, useTransform } from "motion/react";
export function MagneticButton({ children }: { children: React.ReactNode }) {
const ref = useRef<HTMLButtonElement>(null);
const [isHovered, setIsHovered] = useState(false);
const x = useSpring(0, { stiffness: 150, damping: 15 });
const y = useSpring(0, { stiffness: 150, damping: 15 });
const handleMouseMove = (e: React.MouseEvent) => {
if (!ref.current) return;
const rect = ref.current.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
x.set((e.clientX - centerX) * 0.3);
y.set((e.clientY - centerY) * 0.3);
};
const handleMouseLeave = () => {
x.set(0);
y.set(0);
setIsHovered(false);
};
return (
<motion.button
ref={ref}
style={{ x, y }}
onMouseMove={handleMouseMove}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={handleMouseLeave}
>
{children}
</motion.button>
);
}Spring Physics
The magic is in the spring configuration:
stiffness: 150— How quickly it responds. Higher = snappier.damping: 15— How quickly oscillation stops. Lower = bouncier.0.3multiplier — How much the button actually moves. Keep it subtle.
Note: The
motion/reactimport is the modern way to use Motion (previously Framer Motion). This is a drop-in replacement with the same API.
Considerations
- Keep the movement subtle —
0.2–0.4multiplier works best - Use this on CTAs and important buttons, not every clickable element
- Disable on touch devices — magnetic effects don't translate to touch
- Always spring back to
(0, 0)on mouse leave