Magnetic Button
navdeep-singh/magnetic-buttonFreeComponent
A shadcn-compatible button that subtly follows the cursor using spring-based motion.
Install it
npx shadcn@latest add https://ui.navdeepsingh.dev/r/magnetic-button.jsonSource
1'use client'23import * as React from 'react'4import {5 motion,6 useMotionValue,7 useReducedMotion,8 useSpring,9 type SpringOptions,10} from 'motion/react'1112import { Button } from '@/components/ui/button'13import { cn } from '@/lib/utils'1415type MagneticButtonProps = React.ComponentProps<typeof Button> & {16 movement?: number17 wrapperClassName?: string18 springOptions?: SpringOptions19}2021const defaultSpringOptions: SpringOptions = {22 stiffness: 180,23 damping: 10,24 mass: 0.5,25}2627function MagneticButton({28 children,29 className,30 wrapperClassName,31 movement = 6,32 springOptions = defaultSpringOptions,33 disabled,34 onPointerMove,35 onPointerLeave,36 onPointerCancel,37 ...props38}: MagneticButtonProps) {39 const prefersReducedMotion = useReducedMotion()40 const x = useMotionValue(0)41 const y = useMotionValue(0)42 const springX = useSpring(x, springOptions)43 const springY = useSpring(y, springOptions)4445 function resetPosition() {46 x.set(0)47 y.set(0)48 }4950 function handlePointerMove(event: React.PointerEvent<HTMLButtonElement>) {51 onPointerMove?.(event)5253 if (54 event.defaultPrevented ||55 disabled ||56 prefersReducedMotion ||57 event.pointerType !== 'mouse'58 ) {59 return60 }6162 const rect = event.currentTarget.getBoundingClientRect()63 const centerX = rect.width / 264 const centerY = rect.height / 265 const pointerX = event.clientX - rect.left66 const pointerY = event.clientY - rect.top6768 x.set(((pointerX - centerX) / centerX) * movement)69 y.set(((pointerY - centerY) / centerY) * movement)70 }7172 function handlePointerLeave(event: React.PointerEvent<HTMLButtonElement>) {73 onPointerLeave?.(event)7475 if (!event.defaultPrevented) resetPosition()76 }7778 function handlePointerCancel(event: React.PointerEvent<HTMLButtonElement>) {79 onPointerCancel?.(event)8081 if (!event.defaultPrevented) resetPosition()82 }8384 return (85 <motion.span86 data-slot="magnetic-button-wrapper"87 style={{ x: springX, y: springY }}88 whileTap={disabled || prefersReducedMotion ? undefined : { scale: 0.98 }}89 className={cn(90 'inline-flex will-change-transform motion-reduce:transform-none',91 wrapperClassName,92 )}93 >94 <Button95 data-slot="magnetic-button"96 disabled={disabled}97 onPointerMove={handlePointerMove}98 onPointerLeave={handlePointerLeave}99 onPointerCancel={handlePointerCancel}100 className={cn(101// … truncated
What it pulls in
npm packages
Other registry items
Files it writes
More from navdeep-singh
All 64 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Animated Groupanimated-group | navdeep-singh | Components | Free | 1 dep | |
| Animated Numbersanimated-numbers | navdeep-singh | Components | Free | 1 dep | |
| Animated Tabsanimated-tabs | navdeep-singh | Components | Free | 2 deps | |
| Approval Panel Illustrationapproval-panel | navdeep-singh | Components | Free | 2 deps · 2 files | |
| Brand Logobrand-logo | navdeep-singh | Components | Free | 1 dep | |
| Contribution Graphcontribution-graph | navdeep-singh | Components | Free | no deps | |
| Designed for Focus Illustrationdesigned-for-focus-illustration | navdeep-singh | Components | Free | 1 dep | |
| Expandable Cardexpandable-card | navdeep-singh | Components | Free | 4 deps |
