# Magnetic Button

> Magnetic button that follows the pointer nearby and springs back when it leaves.

- Author: Atelier UI
- License: MIT
- Page: https://chromotion.dev/en/components/magnetic-button
- Site: Atelier UI (https://chromotion.dev)

## Install

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

Stack: React + TypeScript + Tailwind CSS v4, shadcn/ui conventions. `cn` comes from `@/lib/utils`.

## Dependencies

No extra dependencies.

## Usage

```tsx title="components/magnetic-button-demo.tsx"
import { MagneticButton } from "@/components/magnetic-button"

export default function MagneticButtonDemo() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-4 rounded-2xl bg-neutral-100 p-6 dark:bg-neutral-900">
      <MagneticButton>Get in touch</MagneticButton>
      <MagneticButton strength={0.55} className="bg-violet-600 text-white">
        Stronger pull
      </MagneticButton>
    </div>
  )
}
```

## Source

```tsx title="components/magnetic-button.tsx"
"use client"

import * as React from "react"

import { cn } from "@/lib/utils"

export interface MagneticButtonProps extends React.ComponentProps<"button"> {
  /** 0-1, how far the button follows the cursor */
  strength?: number
}

export function MagneticButton({
  strength = 0.35,
  onPointerMove,
  onPointerLeave,
  className,
  children,
  ...props
}: MagneticButtonProps) {
  const [offset, setOffset] = React.useState({ x: 0, y: 0 })
  const [active, setActive] = React.useState(false)

  return (
    <span
      className="inline-flex p-8"
      onPointerMove={(event) => {
        const rect = event.currentTarget.getBoundingClientRect()
        setActive(true)
        setOffset({
          x: (event.clientX - (rect.left + rect.width / 2)) * strength,
          y: (event.clientY - (rect.top + rect.height / 2)) * strength,
        })
      }}
      onPointerLeave={() => {
        setActive(false)
        setOffset({ x: 0, y: 0 })
      }}
    >
      <button
        type="button"
        onPointerMove={onPointerMove}
        onPointerLeave={onPointerLeave}
        style={{ transform: `translate(${offset.x}px, ${offset.y}px)` }}
        className={cn(
          "inline-flex h-12 items-center justify-center gap-2 rounded-full bg-foreground px-8 text-sm font-medium text-background select-none",
          active ? "transition-transform duration-100 ease-out" : "transition-transform duration-500 ease-[cubic-bezier(0.34,1.56,0.64,1)]",
          "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:outline-none",
          "disabled:pointer-events-none disabled:opacity-50",
          className
        )}
        {...props}
      >
        {children}
      </button>
    </span>
  )
}
```
