Tilt Card
3D perspective tilt that tracks the cursor.
0.7 KB gzippedDeps: motion/reactReduced-motion safeBase UI & Radix compatible
Tilt me
3D perspective that tracks your cursor.
Installation
npx shadcn@latest add https://velora.colorlib.com/r/tilt-card.jsonInstalls with: motion
Source
"use client";
import { useRef } from "react";
import {
motion,
useMotionValue,
useReducedMotion,
useSpring,
} from "motion/react";
import { cn } from "@/lib/utils";
interface TiltCardProps {
children: React.ReactNode;
className?: string;
/** Maximum tilt in degrees */
maxTilt?: number;
}
/**
* 3D perspective tilt that follows the cursor.
*/
export function TiltCard({ children, className, maxTilt = 8 }: TiltCardProps) {
const ref = useRef<HTMLDivElement>(null);
const reducedMotion = useReducedMotion();
const rotateX = useMotionValue(0);
const rotateY = useMotionValue(0);
const springX = useSpring(rotateX, { stiffness: 220, damping: 18 });
const springY = useSpring(rotateY, { stiffness: 220, damping: 18 });
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
if (reducedMotion) return;
const rect = ref.current?.getBoundingClientRect();
if (!rect) return;
const px = (e.clientX - rect.left) / rect.width - 0.5;
const py = (e.clientY - rect.top) / rect.height - 0.5;
rotateY.set(px * maxTilt * 2);
rotateX.set(-py * maxTilt * 2);
};
const handleMouseLeave = () => {
rotateX.set(0);
rotateY.set(0);
};
return (
<motion.div
ref={ref}
data-slot="tilt-card"
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
style={{
rotateX: springX,
rotateY: springY,
transformPerspective: 800,
}}
className={cn("will-change-transform", className)}
>
{children}
</motion.div>
);
}