Prism
← All primitives

MagneticButton

Interaction

A button gently attracted to the cursor while hovered, springing back on leave. GSAP smoothing; respects reduced-motion.

Requires the .prism-magnetic style from globals.css.

$npx shadcn@latest add https://prism.icglabs.co/r/magnetic-button.json
Dependencies:gsap
View raw manifest →

components/prism/MagneticButton.tsx
"use client";

import { useRef } from "react";
import gsap from "gsap";

/**
 * MagneticButton — an element that's gently attracted to the cursor while hovered,
 * then springs back on leave. GSAP for the smoothing (already a dep). Honors
 * prefers-reduced-motion (no transform applied).
 */
export function MagneticButton({
  children,
  className = "",
  strength = 0.4,
  onClick,
  type = "button",
}: {
  children: React.ReactNode;
  className?: string;
  /** 0–1: how strongly it follows the cursor. */
  strength?: number;
  onClick?: () => void;
  type?: "button" | "submit";
}) {
  const ref = useRef<HTMLButtonElement>(null);

  function onMove(e: React.MouseEvent<HTMLButtonElement>) {
    const el = ref.current;
    if (!el) return;
    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
    const r = el.getBoundingClientRect();
    const x = (e.clientX - (r.left + r.width / 2)) * strength;
    const y = (e.clientY - (r.top + r.height / 2)) * strength;
    gsap.to(el, { x, y, duration: 0.6, ease: "power3.out" });
  }

  function onLeave() {
    const el = ref.current;
    if (!el) return;
    gsap.to(el, { x: 0, y: 0, duration: 0.7, ease: "elastic.out(1, 0.4)" });
  }

  return (
    <button
      ref={ref}
      type={type}
      onClick={onClick}
      onMouseMove={onMove}
      onMouseLeave={onLeave}
      className={`prism-magnetic ${className}`}
    >
      {children}
    </button>
  );
}
Live demo — read-only. Every section is a real, copyable primitive.