Velocity UI
Loading…
Menu

Isometric 3D Card

A realistic glassmorphic credit card that tilts in 3D and flips to show the CVV when needed.

Installation

Add this component to your project using the CLI:

terminal
npx vui-registry-cli-v1 add iso-3d-card

Source Code

iso-3d-card.tsx

"use client"

import { useState, useRef, useEffect } from "react"
import { motion, useMotionValue, useSpring, useTransform } from "framer-motion"
import { CreditCard, Wifi, ShieldCheck, Nfc } from "lucide-react"

export default function Iso3DCard() {
  const [cardNumber, setCardNumber] = useState("4582 3456 7890 1234")
  const [expiry, setExpiry] = useState("12/26")
  const [cvv, setCvv] = useState("123")
  const [name, setName] = useState("VIKAS YADAV")
  const [isFlipped, setIsFlipped] = useState(false)

  // Mouse interaction
  const x = useMotionValue(0)
  const y = useMotionValue(0)

  const mouseX = useSpring(x, { stiffness: 500, damping: 100 })
  const mouseY = useSpring(y, { stiffness: 500, damping: 100 })

  const rotateX = useTransform(mouseY, [-0.5, 0.5], ["-20deg", "20deg"])
  const rotateY = useTransform(mouseX, [-0.5, 0.5], ["20deg", "-20deg"])

  function handleMouseMove({ currentTarget, clientX, clientY }: React.MouseEvent) {
    const { left, top, width, height } = currentTarget.getBoundingClientRect()
    const xPct = (clientX - left) / width - 0.5
    const yPct = (clientY - top) / height - 0.5
    x.set(xPct)
    y.set(yPct)
  }

  function handleMouseLeave() {
    x.set(0)
    y.set(0)
    setIsFlipped(false)
  }

  return (
    <div className="flex min-h-[600px] w-full items-center justify-center bg-neutral-100 dark:bg-neutral-900 perspective-1000 flex-col">
      <div 
        className="relative w-full max-w-lg p-8 perspective-1000"
        onMouseMove={handleMouseMove}
        onMouseLeave={handleMouseLeave}
      >
        <motion.div
          style={{
            rotateX,
            rotateY,
            transformStyle: "preserve-3d",
          }}
          transition={{ type: "spring", stiffness: 400, damping: 30 }}
          className="relative h-64 w-96 mx-auto rounded-3xl shadow-2xl transition-all duration-200"
        >
          {/* Card Front */}
          <div 
            className="absolute inset-0 h-full w-full rounded-3xl bg-gradient-to-br from-neutral-800 to-black p-8 text-white shadow-xl backface-hidden border border-white/10"
            style={{ 
                backfaceVisibility: 'hidden',
                transform: isFlipped ? 'rotateY(180deg)' : 'rotateY(0deg)',
                transition: 'transform 0.6s'
            }}
          >
            {/* Glass effect overlay */}
            <div className="absolute inset-0 bg-white/5 backdrop-blur-sm rounded-3xl pointer-events-none" />
            <div className="absolute -right-10 -top-10 h-40 w-40 rounded-full bg-purple-500/20 blur-3xl" />
            <div className="absolute -left-10 -bottom-10 h-40 w-40 rounded-full bg-blue-500/20 blur-3xl" />

            {/* Content */}
            <div className="relative z-10 flex h-full flex-col justify-between">
              <div className="flex justify-between items-start">
                <Nfc className="w-8 h-8 text-white/80" />
                <Wifi className="h-6 w-6 rotate-90 text-white/60" />
              </div>

              <div className="space-y-6">
                <div className="space-y-1">
                    <p className="font-mono text-2xl tracking-widest drop-shadow-md">{cardNumber}</p>
                    <p className="text-xs text-neutral-400 uppercase tracking-widest">Card Number</p>
                </div>
                
                <div className="flex justify-between items-end">
                    <div>
                        <p className="font-bold text-lg tracking-wider uppercase drop-shadow-md">{name}</p>
                        <p className="text-xs text-neutral-400 uppercase tracking-widest">Card Holder</p>
                    </div>
                    <div>
                        <p className="font-mono text-lg tracking-widest drop-shadow-md">{expiry}</p>
                        <p className="text-xs text-neutral-400 uppercase tracking-widest text-right">Expires</p>
                    </div>
                </div>
              </div>
            </div>
          </div>

          {/* Card Back */}
          <div 
            className="absolute inset-0 h-full w-full rounded-3xl bg-gradient-to-bl from-neutral-900 to-neutral-800 text-white shadow-xl backface-hidden border border-white/10"
            style={{ 
                backfaceVisibility: 'hidden',
                transform: isFlipped ? 'rotateY(0deg)' : 'rotateY(180deg)',
                transition: 'transform 0.6s'
            }}
          >
            <div className="absolute top-8 w-full h-12 bg-black/80" />
            <div className="absolute top-28 left-8 right-8 h-10 bg-white/10 rounded flex items-center justify-end px-4">
                <span className="font-mono text-black font-bold bg-white px-2 py-1 rounded shadow-inner">{cvv}</span>
            </div>
            <div className="absolute bottom-8 left-8 right-8">
                <p className="text-[10px] text-neutral-500 leading-tight">
                    This card is property of Velocity UI. By using this card, you agree to the terms and conditions. If found, please return to the nearest branch.
                </p>
            </div>
          </div>
        </motion.div>

        {/* Form Controls */}
        <div className="mt-12 bg-white dark:bg-neutral-800 p-6 rounded-2xl shadow-lg border border-neutral-200 dark:border-neutral-700 max-w-sm mx-auto">
            <div className="space-y-4">
                <div>
                    <label className="text-xs font-bold text-neutral-500 uppercase">Card Number</label>
                    <input 
                        type="text" 
                        value={cardNumber} 
                        onChange={(e) => setCardNumber(e.target.value)} 
                        className="w-full bg-transparent border-b border-neutral-300 dark:border-neutral-600 py-2 outline-none font-mono focus:border-black dark:focus:border-white transition-colors"
                        onFocus={() => setIsFlipped(false)}
                    />
                </div>
                <div className="grid grid-cols-2 gap-4">
                    <div>
                        <label className="text-xs font-bold text-neutral-500 uppercase">Expiry</label>
                        <input 
                            type="text" 
                            value={expiry} 
                            onChange={(e) => setExpiry(e.target.value)} 
                            className="w-full bg-transparent border-b border-neutral-300 dark:border-neutral-600 py-2 outline-none font-mono focus:border-black dark:focus:border-white transition-colors"
                            onFocus={() => setIsFlipped(false)}
                        />
                    </div>
                    <div>
                        <label className="text-xs font-bold text-neutral-500 uppercase">CVV</label>
                        <input 
                            type="text" 
                            value={cvv} 
                            onChange={(e) => setCvv(e.target.value)} 
                            className="w-full bg-transparent border-b border-neutral-300 dark:border-neutral-600 py-2 outline-none font-mono focus:border-black dark:focus:border-white transition-colors"
                            onFocus={() => setIsFlipped(true)}
                        />
                    </div>
                </div>
                <div>
                    <label className="text-xs font-bold text-neutral-500 uppercase">Name</label>
                    <input 
                        type="text" 
                        value={name} 
                        onChange={(e) => setName(e.target.value)} 
                        className="w-full bg-transparent border-b border-neutral-300 dark:border-neutral-600 py-2 outline-none font-bold uppercase focus:border-black dark:focus:border-white transition-colors"
                        onFocus={() => setIsFlipped(false)}
                    />
                </div>
            </div>
        </div>

      </div>
    </div>
  )
}

Dependencies

  • framer-motion: latest
  • lucide-react: latest
  • clsx: latest
  • tailwind-merge: latest

Props

Component property reference.

NameTypeDefaultDescription
cardNumberstringundefinedInitial card number to display.
expirystringundefinedInitial expiry date (MM/YY).
cvvstringundefinedInitial CVV code.
namestringundefinedCardholder name.
backgroundImagestringundefinedOptional background image URL for the card face.
Context Worth Keeping In Orbit

Most components here are inspired by outstanding libraries and creators in the ecosystem. I don’t claim to be the original author — this is my space for learning, rebuilding, and understanding great work at a deeper level.

I’m still a student of the craft, constantly studying the best and translating what I learn through my own perspective. Every piece reflects curiosity, respect for the community, and small creative touches that feel true to me.

I’ve done my best to credit inspirations properly. If anything is missing or inaccurate, I truly appreciate a message so it can be corrected with care.