Tilt Card

3D perspective tilt with a moving glare highlight.

Kinetiq Pay

4821 •••• •••• 0921

Alex Morgan09/29

Installation

  1. Install the dependencies

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

    components/ui/tilt-card.tsx
    "use client";
    
    import { motion, useMotionTemplate, useMotionValue, useSpring, useTransform } from "motion/react";
    import { Wifi } from "lucide-react";
    import type { ReactNode } from "react";
    
    type TiltCardProps = {
      children: ReactNode;
      className?: string;
      /** Maximum rotation in degrees */
      maxTilt?: number;
    };
    
    export function TiltCard({ children, className = "", maxTilt = 12 }: TiltCardProps) {
      const px = useMotionValue(0.5);
      const py = useMotionValue(0.5);
      const rotateX = useSpring(useTransform(py, [0, 1], [maxTilt, -maxTilt]), { stiffness: 180, damping: 18 });
      const rotateY = useSpring(useTransform(px, [0, 1], [-maxTilt, maxTilt]), { stiffness: 180, damping: 18 });
      const glareX = useTransform(px, [0, 1], [0, 100]);
      const glareY = useTransform(py, [0, 1], [0, 100]);
      const glare = useMotionTemplate`radial-gradient(circle at ${glareX}% ${glareY}%, rgba(255,255,255,0.35), transparent 55%)`;
    
      return (
        <motion.div
          onMouseMove={(e) => {
            const rect = e.currentTarget.getBoundingClientRect();
            px.set((e.clientX - rect.left) / rect.width);
            py.set((e.clientY - rect.top) / rect.height);
          }}
          onMouseLeave={() => {
            px.set(0.5);
            py.set(0.5);
          }}
          style={{ rotateX, rotateY, transformPerspective: 900 }}
          className={`relative overflow-hidden rounded-2xl ${className}`}
        >
          {children}
          <motion.div aria-hidden className="pointer-events-none absolute inset-0" style={{ background: glare }} />
        </motion.div>
      );
    }
    
    export default function Demo() {
      return (
        <TiltCard className="aspect-[1.586] w-80 bg-gradient-to-br from-zinc-900 via-zinc-800 to-blue-900 p-6 text-white shadow-2xl shadow-zinc-900/30">
          <div className="flex h-full flex-col justify-between">
            <div className="flex items-start justify-between">
              <span className="text-sm font-semibold tracking-wide">Kinetiq Pay</span>
              <Wifi className="size-5 rotate-90 opacity-70" />
            </div>
            <div className="h-8 w-11 rounded-md bg-gradient-to-br from-amber-200 to-amber-400" />
            <div>
              <p className="font-mono text-lg tracking-[0.2em]">4821 •••• •••• 0921</p>
              <div className="mt-2 flex justify-between text-xs uppercase tracking-wider text-white/60">
                <span>Alex Morgan</span>
                <span>09/29</span>
              </div>
            </div>
          </div>
        </TiltCard>
      );
    }
    
  3. Use it

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