Flip Card

A 3D card that flips over on click to reveal its back.

Installation

  1. Install the dependencies

    $ npm i motion lucide-react
  2. Copy the source into components/ui/flip-card.tsx

    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&apos;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>
      );
    }
    
  3. Use it

    The file exports the component and a Demo showing typical usage. Import whichever you need.