Magnetic
A component that creates a magnetic attraction effect, making elements follow the cursor with smooth spring animations.
Last updated on
import { Magnetic } from "@/components/ui/magnetic";
export function MagneticDemo() {
return (
<div className="flex min-h-[300px] items-center justify-center">
<Magnetic>
<button className="rounded-lg bg-primary px-6 py-3 font-medium text-primary-foreground text-sm shadow-lg transition-colors hover:bg-primary/90">
Hover Me
</button>
</Magnetic>
</div>
);
}Installation
CLI
npx shadcn@latest add "https://jolyui.dev/r/magnetic"Manual
Install the following dependencies:
npm install motionCopy and paste the following code into your project.
"use client";
import {
motion,
type SpringOptions,
useMotionValue,
useSpring,
} from "motion/react";
import type React from "react";
import { useEffect, useRef, useState } from "react";
const SPRING_CONFIG = { stiffness: 26.7, damping: 4.1, mass: 0.2 };
export type MagneticProps = {
children: React.ReactNode;
intensity?: number;
range?: number;
actionArea?: "self" | "parent" | "global";
springOptions?: SpringOptions;
};
export function Magnetic({
children,
intensity = 0.6,
range = 100,
actionArea = "self",
springOptions = SPRING_CONFIG,
}: MagneticProps) {
const [isHovered, setIsHovered] = useState(false);
const ref = useRef<HTMLDivElement>(null);
const x = useMotionValue(0);
const y = useMotionValue(0);
const springX = useSpring(x, springOptions);
const springY = useSpring(y, springOptions);
useEffect(() => {
const calculateDistance = (e: MouseEvent) => {
if (ref.current) {
const rect = ref.current.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
const distanceX = e.clientX - centerX;
const distanceY = e.clientY - centerY;
const absoluteDistance = Math.sqrt(distanceX ** 2 + distanceY ** 2);
if (isHovered && absoluteDistance <= range) {
const scale = 1 - absoluteDistance / range;
x.set(distanceX * intensity * scale);
y.set(distanceY * intensity * scale);
} else {
x.set(0);
y.set(0);
}
}
};
document.addEventListener("mousemove", calculateDistance);
return () => {
document.removeEventListener("mousemove", calculateDistance);
};
}, [isHovered, intensity, range, x.set, y.set]);
useEffect(() => {
if (actionArea === "parent" && ref.current?.parentElement) {
const parent = ref.current.parentElement;
const handleParentEnter = () => setIsHovered(true);
const handleParentLeave = () => setIsHovered(false);
parent.addEventListener("mouseenter", handleParentEnter);
parent.addEventListener("mouseleave", handleParentLeave);
return () => {
parent.removeEventListener("mouseenter", handleParentEnter);
parent.removeEventListener("mouseleave", handleParentLeave);
};
} else if (actionArea === "global") {
setIsHovered(true);
}
}, [actionArea]);
const handleMouseEnter = () => {
if (actionArea === "self") {
setIsHovered(true);
}
};
const handleMouseLeave = () => {
if (actionArea === "self") {
setIsHovered(false);
x.set(0);
y.set(0);
}
};
return (
<motion.div
ref={ref}
onMouseEnter={actionArea === "self" ? handleMouseEnter : undefined}
onMouseLeave={actionArea === "self" ? handleMouseLeave : undefined}
style={{
x: springX,
y: springY,
}}
>
{children}
</motion.div>
);
}Layout
import { Magnetic } from "@/components/ui/magnetic";
<Magnetic>
<button>Hover Me</button>
</Magnetic>Examples
Intensity
Control the strength of the magnetic effect with the intensity prop. Higher values create stronger attraction.
import { Magnetic } from "@/components/ui/magnetic";
export function MagneticIntensityDemo() {
return (
<div className="flex min-h-[300px] flex-wrap items-center justify-center gap-8">
<Magnetic intensity={0.3}>
<button className="rounded-lg bg-primary px-6 py-3 font-medium text-primary-foreground text-sm shadow-lg">
Low (0.3)
</button>
</Magnetic>
<Magnetic intensity={0.6}>
<button className="rounded-lg bg-primary px-6 py-3 font-medium text-primary-foreground text-sm shadow-lg">
Medium (0.6)
</button>
</Magnetic>
<Magnetic intensity={1.2}>
<button className="rounded-lg bg-primary px-6 py-3 font-medium text-primary-foreground text-sm shadow-lg">
High (1.2)
</button>
</Magnetic>
</div>
);
}Range
Adjust the activation distance with the range prop. This defines how far from the element the effect begins.
import { Magnetic } from "@/components/ui/magnetic";
export function MagneticRangeDemo() {
return (
<div className="flex min-h-[300px] flex-wrap items-center justify-center gap-8">
<Magnetic range={50}>
<button className="rounded-lg bg-primary px-6 py-3 font-medium text-primary-foreground text-sm shadow-lg">
Small Range (50px)
</button>
</Magnetic>
<Magnetic range={100}>
<button className="rounded-lg bg-primary px-6 py-3 font-medium text-primary-foreground text-sm shadow-lg">
Medium Range (100px)
</button>
</Magnetic>
<Magnetic range={200}>
<button className="rounded-lg bg-primary px-6 py-3 font-medium text-primary-foreground text-sm shadow-lg">
Large Range (200px)
</button>
</Magnetic>
</div>
);
}Action Area
Define where the magnetic effect should be triggered using the actionArea prop.
import { Magnetic } from "@/components/ui/magnetic";
export function MagneticActionAreaDemo() {
return (
<div className="flex min-h-[400px] flex-col items-center justify-center gap-12">
<div className="flex flex-col items-center gap-2">
<p className="text-muted-foreground text-sm">Self (hover the button)</p>
<Magnetic actionArea="self">
<button className="rounded-lg bg-primary px-6 py-3 font-medium text-primary-foreground text-sm shadow-lg">
Self Action Area
</button>
</Magnetic>
</div>
<div className="flex flex-col items-center gap-2">
<p className="text-muted-foreground text-sm">
Parent (hover the container)
</p>
<div className="rounded-lg border-2 border-muted-foreground/30 border-dashed p-8">
<Magnetic actionArea="parent">
<button className="rounded-lg bg-primary px-6 py-3 font-medium text-primary-foreground text-sm shadow-lg">
Parent Action Area
</button>
</Magnetic>
</div>
</div>
<div className="flex flex-col items-center gap-2">
<p className="text-muted-foreground text-sm">Global (always active)</p>
<Magnetic actionArea="global">
<button className="rounded-lg bg-primary px-6 py-3 font-medium text-primary-foreground text-sm shadow-lg">
Global Action Area
</button>
</Magnetic>
</div>
</div>
);
}Cards Example
A practical example showing the magnetic effect applied to card components.
import { Magnetic } from "@/components/ui/magnetic";
export function MagneticCardsDemo() {
const cards = [
{ title: "Design", description: "Beautiful UI components", icon: "🎨" },
{
title: "Development",
description: "Clean and efficient code",
icon: "💻",
},
{ title: "Animation", description: "Smooth interactions", icon: "✨" },
];
return (
<div className="flex min-h-[400px] flex-wrap items-center justify-center gap-6 p-8">
{cards.map((card, index) => (
<Magnetic key={index} intensity={0.4} range={120}>
<div className="flex h-48 w-56 flex-col items-center justify-center gap-3 rounded-xl border bg-card p-6 shadow-lg transition-shadow hover:shadow-xl">
<span className="text-4xl">{card.icon}</span>
<h3 className="font-semibold text-lg">{card.title}</h3>
<p className="text-center text-muted-foreground text-sm">
{card.description}
</p>
</div>
</Magnetic>
))}
</div>
);
}API Reference
Magnetic
The main magnetic wrapper component that applies smooth cursor-following animations to its children.
Prop
Type
How is this guide?