buttons
Magnetic Button
A button that magnetically follows the cursor on hover using spring physics.
buttonhoveranimationmagneticinteractive
▶Preview
🖲Usage
example-usage.tsx
import { MagneticButton } from "@/components/kanso/magnetic-button"
export default function MagneticButtonDemo() {
return (
<div className="flex gap-4">
<MagneticButton>Hover Me</MagneticButton>
<MagneticButton variant="outline" magneticStrength={0.4}>
Custom Strength
</MagneticButton>
</div>
)
}↓Installation
1
Create folder & copy source
Create a folder named kanso inside your project's components directory (i.e. components/kanso/). Copy the source code shown in the next section, and paste it into a file named magnetic-button.tsx inside it.
2
Install dependencies
pnpm add framer-motion3
Required helper files
Ensure your project has the following helper files configured:
- →
lib/utils
<>Source Code
magnetic-button.tsx
"use client"
import * as React from "react"
import { motion, useMotionValue, useSpring, type HTMLMotionProps } from "framer-motion"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const magneticButtonVariants = cva(
"relative inline-flex items-center justify-center rounded-lg text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-500 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default:
"border border-zinc-200 bg-zinc-900 px-6 py-2.5 text-white hover:bg-zinc-800 dark:border-zinc-700 dark:bg-white dark:text-zinc-900 dark:hover:bg-zinc-100",
outline:
"border border-zinc-200 bg-transparent px-6 py-2.5 text-zinc-900 hover:bg-zinc-100/60 dark:border-zinc-800 dark:text-zinc-100 dark:hover:bg-zinc-900/60",
},
},
defaultVariants: {
variant: "default",
},
}
)
interface MagneticButtonProps
extends Omit<HTMLMotionProps<"button">, "ref">,
VariantProps<typeof magneticButtonVariants> {
/** Strength of the magnetic pull effect (default: 0.3) */
magneticStrength?: number
}
function MagneticButton({
children,
className,
variant,
magneticStrength = 0.3,
disabled,
onClick,
...props
}: MagneticButtonProps) {
const ref = React.useRef<HTMLButtonElement>(null)
const x = useMotionValue(0)
const y = useMotionValue(0)
const springX = useSpring(x, { stiffness: 150, damping: 15, mass: 0.1 })
const springY = useSpring(y, { stiffness: 150, damping: 15, mass: 0.1 })
const handleMouseMove = React.useCallback(
(e: React.MouseEvent<HTMLButtonElement>) => {
if (!ref.current) return
const rect = ref.current.getBoundingClientRect()
const centerX = rect.left + rect.width / 2
const centerY = rect.top + rect.height / 2
x.set((e.clientX - centerX) * magneticStrength)
y.set((e.clientY - centerY) * magneticStrength)
},
[magneticStrength, x, y]
)
const handleMouseLeave = React.useCallback(() => {
x.set(0)
y.set(0)
}, [x, y])
return (
<motion.button
ref={ref}
style={{ x: springX, y: springY }}
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
onClick={onClick}
disabled={disabled}
className={cn(magneticButtonVariants({ variant, className }))}
{...props}
>
{children}
</motion.button>
)
}
export { MagneticButton }
export type { MagneticButtonProps }
≡Props
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "default" | "outline" | "default" | The visual style variant of the button. |
magneticStrength | number | 0.3 | Strength of the magnetic pull effect (0 to 1). |
childrenrequired | React.ReactNode | — | Content to render inside the button. |
className | string | — | Additional CSS classes to apply. |