Skip to content

3D Flip Card

A card that flips on a real Y-axis rotation to reveal a back face.

Freeplayfulglass
Category
Components
Added
2026-08-22
Deps
1
Updated
2026-09-01
Preview
Hover to flip
Here's the back

Props

Requires framer-motion
"use client";

import { useState } from "react";
import { motion } from "framer-motion";

export function FlipCard({
  frontLabel = "Hover to flip",
  backLabel = "Here's the back",
  trigger = "hover",
}: {
  frontLabel?: string;
  backLabel?: string;
  trigger?: "hover" | "click";
}) {
  const [clicked, setClicked] = useState(false);
  const flipped = trigger === "hover" ? undefined : clicked;

  return (
    <div
      className="[perspective:1000px]"
      onClick={() => trigger === "click" && setClicked((v) => !v)}
    >
      <motion.div
        tabIndex={0}
        role={trigger === "click" ? "button" : undefined}
        aria-pressed={trigger === "click" ? flipped : undefined}
        onKeyDown={(e) => {
          if (trigger === "click" && (e.key === "Enter" || e.key === " ")) {
            e.preventDefault();
            setClicked((v) => !v);
          }
        }}
        className="relative h-40 w-64 cursor-pointer outline-none [transform-style:preserve-3d] focus-visible:ring-2 focus-visible:ring-red-500 focus-visible:ring-offset-2"
        animate={trigger === "click" ? { rotateY: flipped ? 180 : 0 } : undefined}
        whileHover={trigger === "hover" ? { rotateY: 180 } : undefined}
        whileFocus={trigger === "hover" ? { rotateY: 180 } : undefined}
        transition={{ duration: 0.6, ease: [0.65, 0, 0.35, 1] }}
      >
        <div className="absolute inset-0 flex items-center justify-center rounded-2xl border border-neutral-200 bg-white p-6 [backface-visibility:hidden]">
          <span className="text-center text-lg font-bold">{frontLabel}</span>
        </div>
        <div
          className="absolute inset-0 flex items-center justify-center rounded-2xl bg-red-500 p-6 [backface-visibility:hidden]"
          style={{ transform: "rotateY(180deg)" }}
        >
          <span className="text-center text-lg font-bold text-white">{backLabel}</span>
        </div>
      </motion.div>
    </div>
  );
}
Notes

About this component

A flip card exists to show two states of the same object — front and back of an ID, a stat then its source — without a cross-fade that makes the two feel unrelated. This one earns the word "3D": the wrapper carries perspective, the rotating element gets transform-style: preserve-3d, and each face has backface-visibility: hidden, so mid-rotation you get real foreshortening instead of two flat layers dissolving into each other. Skip it for anything that needs to stay legible mid-animation — a rotateY pass through 90 degrees is edge-on and blank, so don't put a live form or a running timer on either face. The trigger prop switches which Framer Motion prop drives the rotation, whileHover for hover mode or a controlled animate for click mode, kept mutually exclusive rather than combined, because an active whileHover fighting a controlled animate on the same rotateY value produces visible jitter. The real limitation is the trigger choice itself: hover has nothing to fire on a touchscreen, so a card shipped hover-only stays permanently front-face on mobile unless you branch to click mode.

Curator’s note

backface-visibility:hidden on both faces is the part people skip and then wonder why the back face shows through mirrored mid-flip — it's what makes this a real flip instead of a see-through spin.

Related

Pairs well with

Matched on shared tags and category — the entries most likely to be used alongside this one.

Featured
NewPro

Glare Tilt Card

components

A card that tilts toward the pointer with a true angular specular sweep.

card3dglare

Tilt Card

components

A 3D card that tilts toward the cursor with spring physics.

card3dcursor
Featured
NewPro

A tile grid that flips on a diagonal wavefront to resolve into one image.

flipgrid3d