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>
  );
};
Welcome to my portfolio! πŸ‘‹
Lo-Fi Beatsfocus mode