# Magnetic Button

The MagneticButton component drifts toward the pointer as it approaches and springs back when the pointer leaves, making calls to action feel responsive before they are even clicked.

- **Category:** Button
- **Docs:** https://uibeats.com/docs/button/magnetic-button
- **Registry:** https://uibeats.com/r/magnetic-button.json
- **License:** MIT
- **npm dependencies:** `motion`

## Installation

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

Or install the dependencies and copy the source below:

```bash
npm install motion
```

## When to use

For the single most important action on a page. The pull only reads as intentional when nothing competes nearby, so avoid two side by side.

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` | `React.ReactNode` | `-` | Button label or content (required) |
| `strength` | `number` | `18` | How far the button travels toward the pointer, in pixels |
| `radius` | `number` | `120` | Distance from the button, in pixels, at which it starts reacting |
| `className` | `string` | `''` | Additional CSS classes for styling |
| `...props` | `ButtonHTMLAttributes` | `-` | Any other native button props, including onClick, type and disabled |

## Example usage

```tsx
"use client";
import { MagneticButton } from "@/components/demo/button/magnetic-button";

const MagneticButtonUsage = () => {
  return (
    <MagneticButton strength={22} onClick={() => {}}>
      Hover me
    </MagneticButton>
  );
};

export default MagneticButtonUsage;
```

## Source

Save as `components/ui/magnetic-button.tsx`.

```tsx
"use client";

import { useRef, type ReactNode, type ComponentPropsWithoutRef } from "react";
import {
  motion,
  useMotionValue,
  useReducedMotion,
  useSpring,
} from "motion/react";

/**
 * Native button props minus the handlers Motion redefines with its own
 * signatures: spreading React's DOM versions onto `motion.button` conflicts.
 */
type NativeButtonProps = Omit<
  ComponentPropsWithoutRef<"button">,
  | "ref"
  | "style"
  | "onAnimationStart"
  | "onAnimationEnd"
  | "onAnimationIteration"
  | "onDrag"
  | "onDragStart"
  | "onDragEnd"
>;

interface MagneticButtonProps extends NativeButtonProps {
  children: ReactNode;
  /** How far the button travels toward the pointer, in pixels. */
  strength?: number;
  /** Distance from the button, in pixels, at which it starts reacting. */
  radius?: number;
  className?: string;
}

/**
 * A button that drifts toward the pointer as it approaches, then springs back
 * when the pointer leaves.
 */
export function MagneticButton({
  children,
  strength = 18,
  radius = 120,
  className = "",
  ...props
}: MagneticButtonProps) {
  const ref = useRef<HTMLButtonElement>(null);
  const prefersReducedMotion = useReducedMotion();

  const spring = { stiffness: 200, damping: 15, mass: 0.4 };
  const x = useSpring(useMotionValue(0), spring);
  const y = useSpring(useMotionValue(0), spring);

  const handlePointerMove = (event: React.PointerEvent<HTMLButtonElement>) => {
    const element = ref.current;
    if (!element || prefersReducedMotion) return;

    const rect = element.getBoundingClientRect();
    const dx = event.clientX - (rect.left + rect.width / 2);
    const dy = event.clientY - (rect.top + rect.height / 2);
    const distance = Math.hypot(dx, dy);

    if (distance > radius) {
      x.set(0);
      y.set(0);
      return;
    }

    // Falls off linearly with distance so the pull eases in.
    const pull = 1 - distance / radius;
    x.set((dx / radius) * strength * pull * 2);
    y.set((dy / radius) * strength * pull * 2);
  };

  const reset = () => {
    x.set(0);
    y.set(0);
  };

  return (
    <motion.button
      ref={ref}
      style={{ x, y }}
      onPointerMove={handlePointerMove}
      onPointerLeave={reset}
      onBlur={reset}
      whileTap={prefersReducedMotion ? undefined : { scale: 0.95 }}
      className={`inline-flex items-center justify-center rounded-full bg-primary px-6 py-2.5 text-sm font-medium text-primary-foreground shadow-lg transition-colors hover:bg-primary/90 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-none ${className}`}
      {...props}
    >
      {children}
    </motion.button>
  );
}

export default MagneticButton;
```

## Credits

Written by [Claude Code](https://claude.com/claude-code).
