Tilt Card
A self-contained card that provides a 3D "lift and tilt" effect on hover, complete with a customizable, mouse-tracking aurora glow.
Premium Product
A clean and professional card ideal for e-commerce or showcasing premium products. It uses a subtle, monochromatic glow and a prominent drop-shadow on a transparent product image to create a sense of tangible quality.
SaaS Pricing Tier
Demonstrates a practical, real-world use case for a pricing page. The effect is customized with the theme's primary color to feel branded and persuasive, drawing the user's eye to the most popular plan.
Creative Portfolio
This variant showcases a more artistic and expressive use of the component. The liftDistance
is customized to make the content (the image) the hero element, lifting dramatically higher than the text for a powerful visual statement. The multi-color gradient adds to the creative feel.
Installation
CLI
Installation via the CLI is coming soon. For now, please follow the manual installation instructions below.
Manual
- Install the following dependencies:
npm install gsap @gsap/react
yarn add gsap @gsap/react
pnpm add gsap @gsap/react
- Copy and paste the following code into
components/ui/tilt-card.tsx
:
"use client";
import React, { useRef } from 'react';
import { useGSAP } from "@gsap/react";
import gsap from "gsap";
import { Card, CardContent, CardFooter, CardHeader } from "@/components/ui/card";
interface TiltCardAnimationProps {
liftDistance?: [number, number, number];
rotationIntensity?: number;
parallaxMultiplier?: number;
glowGradient?: string;
}
interface TiltCardProps {
children: React.ReactNode;
headerContent: React.ReactNode;
footerContent: React.ReactNode;
className?: string;
animationProps?: TiltCardAnimationProps;
}
export function TiltCard({
children,
headerContent,
footerContent,
className,
animationProps = {}
}: TiltCardProps) {
const containerRef = useRef<HTMLDivElement>(null);
const cardRef = useRef<HTMLDivElement>(null);
const headerRef = useRef<HTMLDivElement>(null);
const contentRef = useRef<HTMLDivElement>(null);
const footerRef = useRef<HTMLDivElement>(null);
const glowRef = useRef<HTMLDivElement>(null);
const {
liftDistance =,
rotationIntensity = 8,
parallaxMultiplier = 5,
glowGradient = `radial-gradient(400px circle at var(--x) var(--y), var(--primary) 0%, var(--secondary) 50%, transparent 100%)`,
} = animationProps;
const isFlat = liftDistance.every(d => d === 0);
useGSAP(() => {}, { scope: containerRef });
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
if (!containerRef.current) return;
const { left, top, width, height } = containerRef.current.getBoundingClientRect();
const mouseX = e.clientX - left;
const mouseY = e.clientY - top;
gsap.to(containerRef.current, {
duration: 1,
ease: "power2.out",
'--x': `${mouseX}px`,
'--y': `${mouseY}px`,
});
const rotateX = gsap.utils.mapRange(0, height, -rotationIntensity, rotationIntensity, mouseY);
const rotateY = gsap.utils.mapRange(0, width, rotationIntensity, -rotationIntensity, mouseX);
gsap.to(cardRef.current, { duration: 1, rotateX, rotateY, ease: "power3.out" });
if (!isFlat) {
gsap.to([headerRef.current, contentRef.current, footerRef.current], {
duration: 1,
x: (i) => (rotateY / rotationIntensity) * liftDistance[i] * 0.1 * parallaxMultiplier,
y: (i) => (-rotateX / rotationIntensity) * liftDistance[i] * 0.1 * parallaxMultiplier,
ease: "power3.out",
});
}
};
const handleMouseEnter = () => {
gsap.to(glowRef.current, { duration: 0.3, opacity: 1, ease: "power2.out" });
if (!isFlat) {
gsap.to([headerRef.current, contentRef.current, footerRef.current], {
duration: 0.4,
translateZ: (index) => liftDistance[index],
stagger: 0.05,
ease: "power2.out",
});
}
};
const handleMouseLeave = () => {
gsap.killTweensOf([cardRef.current, headerRef.current, contentRef.current, footerRef.current, glowRef.current, containerRef.current]);
gsap.to(glowRef.current, { duration: 0.3, opacity: 0, ease: "power2.out" });
gsap.to(cardRef.current, { duration: 0.5, rotateX: 0, rotateY: 0, ease: "power3.out" });
if (!isFlat) {
gsap.to([headerRef.current, contentRef.current, footerRef.current], {
duration: 0.5,
translateZ: 0,
x: 0,
y: 0,
stagger: 0.05,
ease: "power3.out",
});
}
};
return (
<div
ref={containerRef}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onMouseMove={handleMouseMove}
style={{ '--x': '50%', '--y': '50%' } as React.CSSProperties}
className={`relative [perspective:1000px] ${className}`}
>
<Card
ref={cardRef}
className="relative z-10 h-full w-full bg-card [transform-style:preserve-3d]"
>
<CardHeader ref={headerRef}>{headerContent}</CardHeader>
<div ref={contentRef}>
<CardContent>{children}</CardContent>
</div>
<CardFooter ref={footerRef}>{footerContent}</CardFooter>
</Card>
<div
ref={glowRef}
className="pointer-events-none absolute inset-0 rounded-xl opacity-0"
style={{ background: glowGradient }}
/>
</div>
);
}
Usage
Import the component and provide content for the headerContent
, footerContent
, and children
props. Customize the animation with the animationProps
object.
import { TiltCard } from "@/components/ui/tilt-card";
import { Button } from "@/components/ui/button";
import { CardTitle, CardDescription } from "@/components/ui/card";
import Image from "next/image";
export default function Example() {
return (
<div className="grid grid-cols-1 gap-8 md:grid-cols-2">
{/* Default Card */}
<TiltCard
className="w-full max-w-sm"
headerContent={<CardTitle>Default Experience</CardTitle>}
footerContent={<Button className="w-full">Learn More</Button>}
>
<p className="text-muted-foreground">
This card uses the default animation settings.
</p>
</TiltCard>
{/* Customized "Flat" Card */}
<TiltCard
className="w-full max-w-sm"
animationProps={{
liftDistance:, // Creates the flat effect
glowGradient: `radial-gradient(circle at 50% 50%, var(--primary) 0%, transparent 70%)`,
}}
headerContent={<CardTitle>Flat Card with Glow</CardTitle>}
footerContent={<Button className="w-full" variant="outline">Select</Button>}
>
<p className="text-muted-foreground">
This card stays flat but gains a static glow on hover.
</p>
</TiltCard>
</div>
);
}
Props
Prop | Type | Default | Description |
---|---|---|---|
children | React.ReactNode | - | The main body content of the card. |
headerContent | React.ReactNode | - | The content for the card's header section. |
footerContent | React.ReactNode | - | The content for the card's footer section. |
className | string | - | Optional classes to apply to the main container. |
animationProps | TiltCardAnimationProps | {} | An object to customize the card's animation behaviors. |
TiltCardAnimationProps
Prop | Type | Default | Description |
---|---|---|---|
liftDistance | [number, number, number] | [60, 40, 20] | The distance each layer lifts on the Z-axis. [header, content, footer] |
rotationIntensity | number | 8 | The maximum rotation angle in degrees for the main tilt effect. |
parallaxMultiplier | number | 5 | Multiplier for the parallax positional shift, enhancing the 3D effect. |
glowGradient | string | radial-gradient(400px circle at var(--x)...) | The full CSS gradient string for the interactive glow effect. |