Magnetic Button
ui-beats/magnetic-buttonFreeComponent
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.
Install it
npx shadcn@latest add https://uibeats.com/r/magnetic-button.jsonSource
1"use client";23import { useRef, type ReactNode, type ComponentPropsWithoutRef } from "react";4import {5 motion,6 useMotionValue,7 useReducedMotion,8 useSpring,9} from "motion/react";1011/**12 * Native button props minus the handlers Motion redefines with its own13 * signatures — spreading React's DOM versions onto `motion.button` conflicts.14 */15type NativeButtonProps = Omit<16 ComponentPropsWithoutRef<"button">,17 | "ref"18 | "style"19 | "onAnimationStart"20 | "onAnimationEnd"21 | "onAnimationIteration"22 | "onDrag"23 | "onDragStart"24 | "onDragEnd"25>;2627interface MagneticButtonProps extends NativeButtonProps {28 children: ReactNode;29 /** How far the button travels toward the pointer, in pixels. */30 strength?: number;31 /** Distance from the button, in pixels, at which it starts reacting. */32 radius?: number;33 className?: string;34}3536/**37 * A button that drifts toward the pointer as it approaches, then springs back38 * when the pointer leaves.39 */40export function MagneticButton({41 children,42 strength = 18,43 radius = 120,44 className = "",45 ...props46}: MagneticButtonProps) {47 const ref = useRef<HTMLButtonElement>(null);48 const prefersReducedMotion = useReducedMotion();4950 const spring = { stiffness: 200, damping: 15, mass: 0.4 };51 const x = useSpring(useMotionValue(0), spring);52 const y = useSpring(useMotionValue(0), spring);5354 const handlePointerMove = (event: React.PointerEvent<HTMLButtonElement>) => {55 const element = ref.current;56 if (!element || prefersReducedMotion) return;5758 const rect = element.getBoundingClientRect();59 const dx = event.clientX - (rect.left + rect.width / 2);60 const dy = event.clientY - (rect.top + rect.height / 2);61 const distance = Math.hypot(dx, dy);6263 if (distance > radius) {64 x.set(0);65 y.set(0);66 return;67 }6869 // Falls off linearly with distance so the pull eases in.70 const pull = 1 - distance / radius;71 x.set((dx / radius) * strength * pull * 2);72 y.set((dy / radius) * strength * pull * 2);73 };7475 const reset = () => {76 x.set(0);77 y.set(0);78 };7980 return (81 <motion.button82 ref={ref}83 style={{ x, y }}84 onPointerMove={handlePointerMove}85 onPointerLeave={reset}86 onBlur={reset}87 whileTap={prefersReducedMotion ? undefined : { scale: 0.95 }}88// … truncated
What it pulls in
npm packages
Files it writes
More from ui-beats
All 26 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Animated Beamanimated-beam | ui-beats | Components | Free | 1 dep | |
| Bouncebounce | ui-beats | Components | Free | 1 dep | |
| Dockdock | ui-beats | Components | Free | 1 dep | |
| Fade Infade-in | ui-beats | Components | Free | 1 dep | |
| Fade In Unblurfade-in-unblur | ui-beats | Components | Free | 1 dep | |
| Flip Cardflip-card | ui-beats | Components | Free | 2 deps | |
| Glowing Cardglowing-card | ui-beats | Components | Free | 1 dep | |
| Gradient Flowgradient-flow | ui-beats | Components | Free | 1 dep |
