Buttons
Five button styles, built with React and Framer Motion. Click "Copy" on any of them to grab the code.
Magnetic Button
Pulls toward the cursor on hover with a spring, snaps back on leave.
Code
import { useRef } from 'react';
import { motion, useMotionValue, useSpring } from 'framer-motion';
const MagneticButton = ({ children, onClick }) => {
const ref = useRef(null);
const x = useMotionValue(0);
const y = useMotionValue(0);
const springConfig = { damping: 15, stiffness: 150, mass: 0.1 };
const springX = useSpring(x, springConfig);
const springY = useSpring(y, springConfig);
const handlePointerMove = (e) => {
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 handlePointerLeave = () => {
x.set(0);
y.set(0);
};
return (
<motion.button
ref={ref}
onPointerMove={handlePointerMove}
onPointerLeave={handlePointerLeave}
onClick={onClick}
style={{ x: springX, y: springY }}
whileTap={{ scale: 0.95 }}
className="px-6 py-3 rounded-full bg-black text-white font-medium shadow-lg"
>
{children}
</motion.button>
);
};Gradient Button
Animated gradient that slides on hover instead of just fading.
Code
const GradientButton = ({ children, onClick }) => (
<button
onClick={onClick}
className="px-6 py-3 rounded-full text-white font-medium
bg-gradient-to-r from-blue-600 via-purple-600 to-pink-600
bg-[length:200%_auto] hover:bg-[position:right_center]
transition-[background-position] duration-500
shadow-lg hover:shadow-purple-500/30"
>
{children}
</button>
);Glass Button
Frosted, translucent β reads best over a colorful or busy background.
Code
const GlassButton = ({ children, onClick }) => (
<button
onClick={onClick}
className="px-6 py-3 rounded-full backdrop-blur-md
bg-white/10 dark:bg-white/5 border border-white/20
text-current font-medium
hover:bg-white/20 dark:hover:bg-white/10
transition-colors shadow-sm"
>
{children}
</button>
);Loading Button
Swaps to a spinner and disables itself while an async action runs.
Code
import { useState } from 'react';
const LoadingButton = ({ children, onClick }) => {
const [loading, setLoading] = useState(false);
const handleClick = async () => {
setLoading(true);
await onClick?.();
setLoading(false);
};
return (
<button
onClick={handleClick}
disabled={loading}
className="px-6 py-3 rounded-full bg-black text-white font-medium
shadow-md disabled:opacity-70 flex items-center gap-2
min-w-[140px] justify-center"
>
{loading && (
<svg className="w-4 h-4 animate-spin" viewBox="0 0 24 24" fill="none">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" />
</svg>
)}
{loading ? 'Loadingβ¦' : children}
</button>
);
};Outline Icon Button
Border-only until hover, when it fills solid and the arrow slides.
Code
const OutlineIconButton = ({ children, onClick }) => (
<button
onClick={onClick}
className="group px-6 py-3 rounded-full border-2 border-black
text-black font-medium flex items-center gap-2
hover:bg-black hover:text-white transition-colors"
>
{children}
<svg
className="w-4 h-4 transform group-hover:translate-x-1 transition-transform"
fill="none" stroke="currentColor" viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M14 5l7 7m0 0l-7 7m7-7H3" />
</svg>
</button>
);Runaway Button
Dodges the cursor every time you get close. Genuinely unclickable β the taunt changes the longer you try.
Code
import { useRef, useState } from 'react';
import { motion } from 'framer-motion';
const TAUNTS = [
'Click me if you can',
'Nice try π',
'So close!',
'Not happening',
'Still trying? π',
"You'll never catch me",
];
const RunawayButton = () => {
const containerRef = useRef(null);
const btnRef = useRef(null);
const [pos, setPos] = useState({ x: 0, y: 0 });
const [attempts, setAttempts] = useState(0);
const dodge = () => {
const containerRect = containerRef.current.getBoundingClientRect();
const btnRect = btnRef.current.getBoundingClientRect();
const maxX = Math.max(containerRect.width - btnRect.width, 0);
const maxY = Math.max(containerRect.height - btnRect.height, 0);
setPos({ x: Math.random() * maxX, y: Math.random() * maxY });
setAttempts((prev) => prev + 1);
};
const taunt = TAUNTS[Math.min(attempts, TAUNTS.length - 1)];
return (
<div ref={containerRef} className="relative w-full h-48">
<motion.button
ref={btnRef}
onMouseEnter={dodge}
onClick={dodge}
animate={{ x: pos.x, y: pos.y }}
transition={{ type: 'spring', stiffness: 300, damping: 20 }}
className="absolute top-0 left-0 px-6 py-3 rounded-full bg-red-500 text-white font-medium shadow-lg"
>
{taunt}
</motion.button>
</div>
);
};