3D Flip Cards
Original · freePricing-style cards that flip in 3D on hover or tap to reveal details on the back, keyboard accessible with a staggered entrance animation.
Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.
npx shadcn@latest add https://webinnoventix.com/r/card-flip.json"use client";
import { useState, type KeyboardEvent as ReactKeyboardEvent } from "react";
import { motion } from "motion/react";
const EASE = [0.16, 1, 0.3, 1] as const;
type FlipItem = {
kicker: string;
title: string;
front: string;
back: string;
price: string;
from: string;
to: string;
};
const ITEMS: FlipItem[] = [
{
kicker: "Starter",
title: "Launch kit",
front: "Everything a small team needs to ship the first version and start learning fast.",
back: "Unlimited projects, three seats, community support and a friendly onboarding call to get you moving.",
price: "£19",
from: "#6366f1",
to: "#8b5cf6",
},
{
kicker: "Growth",
title: "Scale suite",
front: "Automation, roles and analytics for teams that have found their rhythm.",
back: "Ten seats, advanced permissions, priority support and reporting your stakeholders will actually read.",
price: "£49",
from: "#0ea5e9",
to: "#22d3ee",
},
{
kicker: "Studio",
title: "Creator pack",
front: "A polished toolkit for design-led teams who care about every pixel.",
back: "Custom themes, brand tokens, motion presets and a shared library that keeps everyone on the same page.",
price: "£39",
from: "#f43f5e",
to: "#fb923c",
},
];
function FlipCard({ item, index }: { item: FlipItem; index: number }) {
const [flipped, setFlipped] = useState(false);
function onKey(e: ReactKeyboardEvent<HTMLDivElement>) {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
setFlipped((f) => !f);
}
}
return (
<motion.div
initial={{ opacity: 0, y: 40 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-60px" }}
transition={{ duration: 0.6, delay: index * 0.12, ease: EASE }}
style={{ perspective: 1300 }}
>
<div
role="button"
tabIndex={0}
aria-pressed={flipped}
aria-label={`${item.title}. Activate to see the details.`}
onMouseEnter={() => setFlipped(true)}
onMouseLeave={() => setFlipped(false)}
onClick={() => setFlipped((f) => !f)}
onKeyDown={onKey}
className="relative h-80 w-full cursor-pointer rounded-3xl outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-4 focus-visible:ring-offset-zinc-50 dark:focus-visible:ring-offset-zinc-950"
>
<motion.div
animate={{ rotateY: flipped ? 180 : 0 }}
transition={{ duration: 0.7, ease: EASE }}
style={{ transformStyle: "preserve-3d" }}
className="relative h-full w-full"
>
{/* front */}
<div
style={{
backfaceVisibility: "hidden",
backgroundImage: `linear-gradient(135deg, ${item.from}, ${item.to})`,
}}
className="absolute inset-0 flex flex-col justify-between overflow-hidden rounded-3xl p-7 text-white shadow-xl shadow-zinc-900/10"
>
<div
aria-hidden="true"
className="pointer-events-none absolute -right-10 -top-10 h-40 w-40 rounded-full bg-white/20 blur-2xl"
/>
<motion.div
aria-hidden="true"
animate={{ y: [0, -12, 0] }}
transition={{ duration: 5, repeat: Infinity, ease: "easeInOut" }}
className="pointer-events-none absolute bottom-8 right-6 h-16 w-16 rounded-2xl border border-white/30 bg-white/10 backdrop-blur-sm"
/>
<div className="relative">
<span className="inline-flex items-center gap-2 rounded-full bg-white/20 px-3 py-1 text-xs font-semibold uppercase tracking-widest backdrop-blur-sm">
{item.kicker}
</span>
<h3 className="mt-5 text-2xl font-bold">{item.title}</h3>
<p className="mt-3 max-w-[16rem] text-sm leading-relaxed text-white/85">
{item.front}
</p>
</div>
<div className="relative flex items-center gap-2 text-sm font-medium text-white/90">
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
className="h-4 w-4"
aria-hidden="true"
>
<path d="M3 12a9 9 0 1 0 9-9M3 12l3-3M3 12l3 3" />
</svg>
Hover or tap to flip
</div>
</div>
{/* back */}
<div
style={{
backfaceVisibility: "hidden",
transform: "rotateY(180deg)",
}}
className="absolute inset-0 flex flex-col justify-between overflow-hidden rounded-3xl border border-zinc-200 bg-white p-7 shadow-xl shadow-zinc-900/10 dark:border-zinc-800 dark:bg-zinc-900"
>
<div
aria-hidden="true"
style={{ backgroundImage: `linear-gradient(135deg, ${item.from}, ${item.to})` }}
className="pointer-events-none absolute -left-12 -top-12 h-40 w-40 rounded-full opacity-20 blur-2xl"
/>
<div className="relative">
<h3 className="text-lg font-bold text-zinc-900 dark:text-white">
What is inside
</h3>
<p className="mt-3 text-sm leading-relaxed text-zinc-600 dark:text-zinc-400">
{item.back}
</p>
</div>
<div className="relative flex items-end justify-between">
<div>
<div className="flex items-baseline gap-1">
<span className="text-3xl font-bold text-zinc-900 dark:text-white">
{item.price}
</span>
<span className="text-sm text-zinc-500 dark:text-zinc-500">
/month
</span>
</div>
</div>
<span
style={{ backgroundImage: `linear-gradient(135deg, ${item.from}, ${item.to})` }}
className="inline-flex items-center gap-1.5 rounded-full px-4 py-2 text-sm font-semibold text-white shadow-lg"
>
Choose plan
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
className="h-4 w-4"
aria-hidden="true"
>
<path d="M5 12h14M13 6l6 6-6 6" />
</svg>
</span>
</div>
</div>
</motion.div>
</div>
</motion.div>
);
}
export default function CardFlip() {
return (
<section className="bg-zinc-50 px-6 py-20 dark:bg-zinc-950 md:py-28">
<div className="mx-auto max-w-6xl">
<div className="mx-auto max-w-2xl text-center">
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6, ease: EASE }}
className="text-balance text-3xl font-bold tracking-tight text-zinc-900 sm:text-4xl dark:text-white"
>
Two sides to every card
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.1, ease: EASE }}
className="mx-auto mt-4 max-w-lg text-zinc-600 dark:text-zinc-400"
>
Hover with a mouse or tap on touch to flip each card and reveal the
detail behind the headline. Fully keyboard friendly too.
</motion.p>
</div>
<div className="mt-14 grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
{ITEMS.map((item, i) => (
<FlipCard key={item.title} item={item} index={i} />
))}
</div>
</div>
</section>
);
}Dependencies
Licence
Built by Web Innoventix. Free for personal and commercial use, no attribution required.
Built by Web Innoventix
Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.
Get a free quoteSimilar components
Browse all →
3D Tilt Cards
OriginalA responsive grid of cards that tilt in 3D toward your cursor with a light glare and lifted depth layers, powered by Framer Motion springs.

Spotlight Follow Cards
OriginalFeature cards where a soft radial spotlight and a glowing border chase the cursor on hover, with a staggered scroll reveal and an animated shimmer line.

Animated Gradient Border Cards
OriginalCards wrapped in a live conic gradient border that rotates and speeds up on hover, with a shimmer sweep and a scrolling tag marquee.

Glow Effect Card
primitivesA reusable glow effect card for React and Tailwind, with hover and motion.

Neon Gradient Card
gradient-card.tsxA reusable neon gradient card for React and Tailwind, with hover and motion.

Spotlight Magic Card
card.tsxA reusable spotlight magic card for React and Tailwind, with hover and motion.

Tilt Spotlight Card
primitivesA reusable tilt spotlight card for React and Tailwind, with hover and motion.

Spotlight Hero
OriginalA centred hero with a soft radial spotlight, badge and dual call-to-action.

Split Hero
OriginalA two-column hero pairing a headline and CTAs with a product mock and social proof.

Gradient Spotlight Hero
OriginalA minimal centred hero with a soft gradient-mesh backdrop, announcement pill and dual call-to-action buttons.

App Preview Hero
OriginalA centred hero that pairs headline copy with a realistic product dashboard mock built entirely from markup, complete with browser chrome and a floating notification card.

Waitlist Capture Hero
OriginalA dark, focused hero with an inline email capture form and avatar social proof, ready for pre-launch waitlists.

