Please use Desktop to view and interact with components
Components600mseaseInOut
Credit Card
3D animated credit card with flip animation to show CVV
card3dflippaymentcredit
Useto navigate between components
Preview
Card Number
4532 123 4 56 78 9 010
Cardholder Name
JOHN DOE
Expires
12/24
CVV
123
Code
TypeScript + React
'use client'
import { motion, useMotionValue, useSpring, useTransform } from 'framer-motion'
import { useState } from 'react'
import { CreditCard as CreditCardIcon } from 'lucide-react'
interface CreditCardProps {
cardNumber?: string
cardholderName?: string
expiryDate?: string
cvv?: string
}
export function CreditCard({
cardNumber = '4532 1234 5678 9010',
cardholderName = 'JOHN DOE',
expiryDate = '12/24',
cvv = '123',
}: CreditCardProps) {
const [isFlipped, setIsFlipped] = useState(false)
const x = useMotionValue(0)
const y = useMotionValue(0)
const rotateX = useSpring(useTransform(y, [-0.5, 0.5], [7.5, -7.5]), {
stiffness: 300,
damping: 30,
})
const rotateY = useSpring(useTransform(x, [-0.5, 0.5], [-7.5, 7.5]), {
stiffness: 300,
damping: 30,
})
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
const rect = e.currentTarget.getBoundingClientRect()
const width = rect.width
const height = rect.height
const mouseX = e.clientX - rect.left
const mouseY = e.clientY - rect.top
const xPct = mouseX / width - 0.5
const yPct = mouseY / height - 0.5
x.set(xPct)
y.set(yPct)
}
const handleMouseLeave = () => {
x.set(0)
y.set(0)
}
const formatCardNumber = (number: string) => {
return number.replace(/(.{4})/g, '$1 ').trim()
}
return (
<div className="flex items-center justify-center p-8 perspective-1000">
<motion.div
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
onClick={() => setIsFlipped(!isFlipped)}
className="relative w-[380px] h-[240px] cursor-pointer"
style={{
rotateX,
rotateY,
transformStyle: 'preserve-3d',
}}
whileHover={{ scale: 1.02 }}
transition={{ type: 'spring', stiffness: 300, damping: 30 }}
>
{/* Front of Card */}
<motion.div
className="absolute inset-0 rounded-2xl p-6 flex flex-col justify-between shadow-2xl"
style={{
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
backfaceVisibility: 'hidden',
WebkitBackfaceVisibility: 'hidden',
}}
animate={{
rotateY: isFlipped ? 180 : 0,
}}
transition={{ duration: 0.6, ease: 'easeInOut' }}
>
{/* Card Brand & Chip */}
<div className="flex items-start justify-between">
<div className="relative">
<div className="w-12 h-10 bg-gradient-to-br from-yellow-400/20 to-yellow-600/20 rounded-md flex items-center justify-center border border-yellow-400/30">
<div className="w-8 h-6 bg-gradient-to-br from-yellow-300/40 to-yellow-500/40 rounded-sm" />
</div>
<div className="absolute -bottom-1 left-0 right-0 h-1 bg-gradient-to-r from-yellow-400/30 to-transparent rounded-full" />
</div>
<CreditCardIcon className="w-8 h-8 text-white/80" />
</div>
{/* Card Number */}
<div className="space-y-2">
<div className="text-white/60 text-xs uppercase tracking-wider">
Card Number
</div>
<div className="text-white text-2xl font-mono tracking-wider">
{formatCardNumber(cardNumber)}
</div>
</div>
{/* Cardholder & Expiry */}
<div className="flex items-end justify-between">
<div className="space-y-1">
<div className="text-white/60 text-xs uppercase tracking-wider">
Cardholder Name
</div>
<div className="text-white text-base font-semibold uppercase tracking-wider">
{cardholderName}
</div>
</div>
<div className="space-y-1 text-right">
<div className="text-white/60 text-xs uppercase tracking-wider">
Expires
</div>
<div className="text-white text-base font-semibold">
{expiryDate}
</div>
</div>
</div>
{/* Decorative Circles */}
<div className="absolute top-4 right-4 w-20 h-20 rounded-full bg-white/10 blur-xl" />
<div className="absolute bottom-4 left-4 w-16 h-16 rounded-full bg-white/10 blur-xl" />
</motion.div>
{/* Back of Card */}
<motion.div
className="absolute inset-0 rounded-2xl p-6 flex flex-col justify-between shadow-2xl"
style={{
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
backfaceVisibility: 'hidden',
WebkitBackfaceVisibility: 'hidden',
rotateY: 180,
}}
animate={{
rotateY: isFlipped ? 0 : 180,
}}
transition={{ duration: 0.6, ease: 'easeInOut' }}
>
{/* Magnetic Strip */}
<div className="absolute top-8 left-0 right-0 h-12 bg-black/40" />
{/* CVV Section */}
<div className="mt-20 space-y-2">
<div className="flex items-center justify-end gap-2">
<div className="text-white/60 text-xs">CVV</div>
<div className="bg-white/90 px-4 py-2 rounded text-black font-mono font-semibold text-lg min-w-[80px] text-center">
{cvv}
</div>
</div>
</div>
{/* Card Brand (Back) */}
<div className="flex items-center justify-end">
<CreditCardIcon className="w-8 h-8 text-white/80" />
</div>
{/* Decorative Circles */}
<div className="absolute top-4 left-4 w-20 h-20 rounded-full bg-white/10 blur-xl" />
<div className="absolute bottom-4 right-4 w-16 h-16 rounded-full bg-white/10 blur-xl" />
</motion.div>
</motion.div>
</div>
)
}
How to Use
- 1Install Framer Motion:
npm install framer-motion - 2Copy the code from above
- 3Paste it into your project and customize as needed
- 4Colors are customizable via Tailwind CSS classes. The default theme uses dark mode colors defined in your globals.css file