# Magnetic Button — Animated React Button

> A button that leans toward the cursor with spring physics and snaps back on leave.

- Category: [Buttons](https://pixelpapa.com/components/buttons.md)
- Tags: button, hover, spring, magnetic
- Dependencies: lucide-react, motion (plus React and Tailwind CSS v4)
- Last updated: 2026-11-13
- Live preview: https://pixelpapa.com/components/buttons/magnetic-button

## Install

```bash
npx shadcn@latest add "https://pixelpapa.com/r/magnetic-button.json"
```

The file is written to `components/pixelpapa/magnetic-button.tsx`. To install manually, run `npm install lucide-react motion` and copy the source below into your project. It imports `cn` from `@/lib/utils`, which every shadcn project has.

## Source

```tsx
"use client";

import { ArrowUpRight } from "lucide-react";
import { motion, useMotionValue, useSpring, useTransform } from "motion/react";
import { useRef, type ComponentProps, type PointerEvent } from "react";
import { cn } from "@/lib/utils";

type MagneticButtonProps = ComponentProps<typeof motion.button> & {
  /** How far the button travels toward the cursor, 0–1. */
  strength?: number;
};

export function MagneticButton({ strength = 0.35, className, children, ...props }: MagneticButtonProps) {
  const ref = useRef<HTMLButtonElement>(null);
  const x = useMotionValue(0);
  const y = useMotionValue(0);
  const sx = useSpring(x, { stiffness: 220, damping: 16, mass: 0.4 });
  const sy = useSpring(y, { stiffness: 220, damping: 16, mass: 0.4 });
  // The label moves a little further than the body, which sells the depth.
  const lx = useTransform(sx, (v) => v * 0.35);
  const ly = useTransform(sy, (v) => v * 0.35);

  function onPointerMove(e: PointerEvent<HTMLButtonElement>) {
    const r = ref.current?.getBoundingClientRect();
    if (!r) return;
    x.set((e.clientX - (r.left + r.width / 2)) * strength);
    y.set((e.clientY - (r.top + r.height / 2)) * strength);
  }

  function reset() {
    x.set(0);
    y.set(0);
  }

  return (
    <motion.button
      ref={ref}
      style={{ x: sx, y: sy }}
      onPointerMove={onPointerMove}
      onPointerLeave={reset}
      whileTap={{ scale: 0.96 }}
      className={cn(
        "group relative inline-flex h-12 items-center gap-2 rounded-full bg-primary px-6 text-sm font-medium text-primary-foreground shadow-lg shadow-primary/25 outline-offset-4",
        className,
      )}
      {...props}
    >
      <motion.span style={{ x: lx, y: ly }} className="inline-flex items-center gap-2">
        {children}
      </motion.span>
    </motion.button>
  );
}

export default function MagneticButtonDemo() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-6 p-10">
      <MagneticButton>
        Start building
        <ArrowUpRight className="size-4 transition-transform duration-300 group-hover:translate-x-0.5 group-hover:-translate-y-0.5" />
      </MagneticButton>
      <MagneticButton
        strength={0.5}
        className="border border-border bg-background text-foreground shadow-none hover:bg-muted"
      >
        Read the docs
      </MagneticButton>
    </div>
  );
}
```

## AI prompt

```text
Add the "Magnetic Button" component from PixelPapa UI to this project.

What it is: A button that leans toward the cursor with spring physics and snaps back on leave.
Category: Buttons. Depends on: lucide-react, motion.

1. Install it with the shadcn CLI:
   npx shadcn@latest add "https://pixelpapa.com/r/magnetic-button.json"
   (If the CLI is unavailable, fetch that URL, write files[0].content to components/pixelpapa/magnetic-button.tsx and install the listed dependencies.)
2. Import it from "@/components/pixelpapa/magnetic-button" and place it where I describe below.
3. Keep its animation timings and accessibility attributes. Replace the demo copy and data with content that fits my product, and use my existing theme tokens (bg-background, text-foreground, bg-primary, border-border) rather than hard-coded colours.

Where to use it: <describe the page and position here>
```

## More buttons

- [Button 3D Press](https://pixelpapa.com/components/buttons/button-3d-press.md): A raised key with a real edge and cast shadow: it lifts slightly on hover, sinks hard on press and bounces back on release.
- [Button Async States](https://pixelpapa.com/components/buttons/button-async-states.md) (Pro): Runs a promise and morphs through idle, a collapsed spinner, then a success or error label, animating its width between each.
- [Button Border Beam](https://pixelpapa.com/components/buttons/button-border-beam.md) (Pro): A comet of light laps the button's border on a loop, with a soft halo that follows the head of the beam.
- [Button Confetti Burst](https://pixelpapa.com/components/buttons/button-confetti-burst.md) (Pro): Throws a fan of DOM confetti from the exact point you clicked; pieces arc up, tumble and fall under gravity. No canvas.
- [Button Copy Feedback](https://pixelpapa.com/components/buttons/button-copy-feedback.md) (Pro): Copies a value to the clipboard, then swaps its icon for a drawn check and rolls the label to "Copied" before settling back.
- [Button Gradient Border Glow](https://pixelpapa.com/components/buttons/button-gradient-border-glow.md) (Pro): A flowing gradient outline with a blurred twin underneath; the glow brightens on hover and leans toward the cursor.
