Display
Credit Card
A flippable bank card display — masked number, holder and expiry on the front, magstripe and a hidden CVC placeholder on the back.
Preview in your theme
Loading preview…
Installation
npx shadcn@latest add https://ui.zyeon.ai/r/credit-card.jsonPrompt
The prompt behind this component — paste it into your AI assistant to recreate or adapt it.
Build a React + TypeScript + Tailwind "CreditCard" component (no runtime
dependencies beyond React and the shared cn() utility).
Contract
- Export a forwardRef<HTMLDivElement, CreditCardProps> extending
React.HTMLAttributes<HTMLDivElement>.
- Props: holder: string, number: string (full PAN in — the component does
the redaction, callers don't need to pre-truncate), expiry: string (e.g.
"09/28"), brand?: string (a plain-text wordmark like "VISA", not a logo
image), flippable?: boolean (default true), className.
- No cvc prop at all — the component never receives or displays a real CVC,
only a static "•••" placeholder on the back. This is a display-only
component, not a PCI-scoped payment form.
Behavior
- Mask the number: strip non-digits, keep the last 4, always render
"•••• •••• •••• 1234". Never render digits 1–12.
- Click, Enter or Space (when flippable) toggles a `flipped` boolean that
rotates an inner wrapper 180° around the Y axis to reveal the back;
clicking again flips it back.
- The root is a perspective container; the rotating inner element carries
transform-3d (transform-style: preserve-3d) plus a ~500ms transition.
Each face is absolute inset-0 backface-hidden, with the back face
pre-rotated 180° so it lands right-side-up after the flip.
- Under prefers-reduced-motion: reduce, drop the transition duration to 0
(instant face swap, no animated rotation) and drop the perspective too —
reduced-motion users get a plain instant switch, never a spinning card.
- When flippable is false: no role/tabIndex/aria-pressed/click handler at
all — a static, non-interactive front-only display (the back face still
exists in the DOM but is never reachable).
- Whichever face is rotated away is aria-hidden, so screen readers only
ever see the face that's visually showing.
Rendering & styling
- Card face: bg-gradient-to-br from-primary via-primary/80 to-primary/60
text-primary-foreground — no hardcoded colors, so the card re-themes with
the host palette and dark mode for free.
- Chip: a small 2×2 grid of rounded squares, bg-primary-foreground/80
container with bg-primary/50 pads — CSS only, no image.
- Magstripe: a full-width bg-foreground/80 bar near the top of the back
face. Signature strip: a bg-primary-foreground/90 bar; the CVC
placeholder sits in a small bg-primary-foreground/90 chip with
text-foreground.
- Number is font-mono tracking-widest. Merge className via cn().
- Root sizing: aspect-[8/5] max-w-sm, so it drops into a settings row or a
grid cell at a predictable shape.
Customization levers
- Color scheme: swap the gradient for its inverse — bg-foreground
text-background — for a dark/mono card instead of the primary-tinted one.
- Size: max-w-sm is the only sizing knob; override via className, the
aspect ratio keeps the layout correct at any width.
- Disable flip entirely: flippable={false} for a purely static card when
you never need the back (e.g. a compact list row).
- Brand: the brand slot is plain text on purpose (no bundled network
logos); swap it for an <img> at the call site if you want real card
network marks.
- Never wrap this in a real payment form: for actual PAN/CVC capture use
Stripe Elements (or your processor's hosted fields) — this component,
and any hand-rolled input, is display-only and out of PCI scope by
design.Concepts
- Click/keyboard flip —
role="button"+tabIndex+ manualonKeyDownfor Enter/Space; it's a plaindiv, not a real<button>, so key handling has to be wired by hand. - 3D flip via CSS transforms —
perspectiveon the parent,transform-3d+rotate-y-180+ transition on the flipper,backface-hiddenon each face so only the front-facing side ever paints. - PAN masking — the full card number goes in, only the last 4 digits ever render; masking happens inside the component regardless of what the caller passes in.
- Reduced-motion honesty — under
prefers-reduced-motion, the flip becomes an instant face swap with zero transition and no perspective depth, never a spinning card. - Display-only, not a payment form — there is no
cvcprop; this renders card data, it never captures it. Real PAN/CVC entry belongs to Stripe Elements or an equivalent hosted-fields form.