Flip Card
A 3D card that flips over on click to reveal its back.
components/ui/flip-card.tsx
"use client";
import { motion } from "motion/react";
import { RotateCw } from "lucide-react";
import { useState, type ReactNode } from "react";
type FlipCardProps = {
front: ReactNode;
back: ReactNode;
className?: string;
label?: string;
};
export function FlipCard({ front, back, className = "h-80 w-64", label = "Flip card" }: FlipCardProps) {
const [flipped, setFlipped] = useState(false);
return (
<button
onClick={() => setFlipped((f) => !f)}
aria-pressed={flipped}
aria-label={label}
className={`group relative block text-left [perspective:1200px] ${className}`}
>
<motion.div
className="relative size-full [transform-style:preserve-3d]"
animate={{ rotateY: flipped ? 180 : 0 }}
transition={{ type: "spring", stiffness: 180, damping: 20 }}
>
<div className="absolute inset-0 [backface-visibility:hidden]">{front}</div>
<div className="absolute inset-0 [backface-visibility:hidden] [transform:rotateY(180deg)]">{back}</div>
</motion.div>
</button>
);
}
export default function Demo() {
return (
<div className="flex flex-col items-center gap-3">
<FlipCard
front={
<div className="flex size-full flex-col justify-between rounded-3xl bg-gradient-to-br from-zinc-900 to-zinc-700 p-7 text-white shadow-xl">
<span className="text-sm text-zinc-400">Pro plan</span>
<div>
<p className="text-5xl font-semibold tracking-tight">$24</p>
<p className="mt-1 text-sm text-zinc-400">per seat / month</p>
</div>
<span className="flex items-center gap-1.5 text-xs text-zinc-400">
<RotateCw className="size-3.5" /> Click to see what's included
</span>
</div>
}
back={
<div className="flex size-full flex-col justify-center gap-3 rounded-3xl border border-zinc-200 bg-white p-7 shadow-xl">
<p className="font-semibold text-zinc-900">Everything in Pro</p>
{["Unlimited projects", "Advanced analytics", "Priority support", "Custom domains", "SSO"].map((f) => (
<p key={f} className="flex items-center gap-2 text-sm text-zinc-600">
<span className="size-1.5 rounded-full bg-blue-600" /> {f}
</p>
))}
</div>
}
/>
</div>
);
}
Installation
Install the dependencies
$ npm i motion lucide-reactCopy the source into
components/ui/flip-card.tsxcomponents/ui/flip-card.tsx"use client"; import { motion } from "motion/react"; import { RotateCw } from "lucide-react"; import { useState, type ReactNode } from "react"; type FlipCardProps = { front: ReactNode; back: ReactNode; className?: string; label?: string; }; export function FlipCard({ front, back, className = "h-80 w-64", label = "Flip card" }: FlipCardProps) { const [flipped, setFlipped] = useState(false); return ( <button onClick={() => setFlipped((f) => !f)} aria-pressed={flipped} aria-label={label} className={`group relative block text-left [perspective:1200px] ${className}`} > <motion.div className="relative size-full [transform-style:preserve-3d]" animate={{ rotateY: flipped ? 180 : 0 }} transition={{ type: "spring", stiffness: 180, damping: 20 }} > <div className="absolute inset-0 [backface-visibility:hidden]">{front}</div> <div className="absolute inset-0 [backface-visibility:hidden] [transform:rotateY(180deg)]">{back}</div> </motion.div> </button> ); } export default function Demo() { return ( <div className="flex flex-col items-center gap-3"> <FlipCard front={ <div className="flex size-full flex-col justify-between rounded-3xl bg-gradient-to-br from-zinc-900 to-zinc-700 p-7 text-white shadow-xl"> <span className="text-sm text-zinc-400">Pro plan</span> <div> <p className="text-5xl font-semibold tracking-tight">$24</p> <p className="mt-1 text-sm text-zinc-400">per seat / month</p> </div> <span className="flex items-center gap-1.5 text-xs text-zinc-400"> <RotateCw className="size-3.5" /> Click to see what's included </span> </div> } back={ <div className="flex size-full flex-col justify-center gap-3 rounded-3xl border border-zinc-200 bg-white p-7 shadow-xl"> <p className="font-semibold text-zinc-900">Everything in Pro</p> {["Unlimited projects", "Advanced analytics", "Priority support", "Custom domains", "SSO"].map((f) => ( <p key={f} className="flex items-center gap-2 text-sm text-zinc-600"> <span className="size-1.5 rounded-full bg-blue-600" /> {f} </p> ))} </div> } /> </div> ); }Use it
The file exports the component and a
Demoshowing typical usage. Import whichever you need.