Buttons
Magnetix
A button that leans toward the pointer. The label leans a little further, so it feels pulled, not dragged.
Download
- React
- Tailwind CSS v4
- motion
components/ui/magnetix.tsx
"use client";
import { useRef, type ReactNode } from "react";
import { motion, useReducedMotion, useSpring } from "motion/react";
type MagnetixProps = {
children: ReactNode;
className?: string;
onClick?: () => void;
/** How far the button follows the pointer, 0 to 1. */
strength?: number;
/** How far the label follows, on top of the button. */
labelStrength?: number;
/** Extra pixels around the button where the pull starts. */
field?: number;
};
const SPRING = { stiffness: 220, damping: 16, mass: 0.6 };
/* Magnetix: the button leans toward the pointer, its label leans further. */
export function Magnetix({
children,
className = "rounded-full bg-[#f6f1ff] px-9 py-4 text-[15px] font-medium text-[#1a1530] shadow-[0_12px_40px_-12px_#a78bfa]",
onClick,
strength = 0.35,
labelStrength = 0.15,
field = 40,
}: MagnetixProps) {
const ref = useRef<HTMLButtonElement>(null);
const reduce = useReducedMotion();
const x = useSpring(0, SPRING);
const y = useSpring(0, SPRING);
const lx = useSpring(0, SPRING);
const ly = useSpring(0, SPRING);
const onMove = (e: React.PointerEvent) => {
if (reduce || !ref.current) return;
const r = ref.current.getBoundingClientRect();
const dx = e.clientX - (r.left + r.width / 2);
const dy = e.clientY - (r.top + r.height / 2);
x.set(dx * strength);
y.set(dy * strength);
lx.set(dx * labelStrength);
ly.set(dy * labelStrength);
};
const reset = () => [x, y, lx, ly].forEach((v) => v.set(0));
return (
<span onPointerMove={onMove} onPointerLeave={reset} style={{ display: "inline-block", padding: field, margin: -field }}>
<motion.button ref={ref} type="button" onClick={onClick} style={{ x, y }} className={className}>
<motion.span style={{ x: lx, y: ly, display: "inline-block" }}>{children}</motion.span>
</motion.button>
</span>
);
}
1. Install the dependency.
npm i motion2. Paste the Code tab into
components/ui/magnetix.tsx. The styles are Tailwind CSS v4 classes.3. Use it. This is the preview above:
app/magnetix-demo.tsx
import { Magnetix } from "@/components/ui/magnetix"; export default function MagnetixExample() { return ( <div className="grid h-full place-items-center bg-[radial-gradient(circle_at_50%_60%,#1d1a33,#0d0c14_70%)]"> <Magnetix field={120}>Pull me closer</Magnetix> </div> ); }
Props
| Name | Type | Default | What it does |
|---|---|---|---|
| children | ReactNode | — | Button label. |
| onClick | () => void | — | Click handler. |
| strength | number | 0.35 | How far the button follows the pointer, 0 to 1. |
| labelStrength | number | 0.15 | How far the label follows, on top of the button. |
| field | number | 40 | Extra pixels around the button where the pull starts. |
| className | string | light pill | Replaces the default look. |