Back to all components
3D Card
A sleek, interactive 3D card that tilts and reacts to cursor movement for an immersive UI effect.
1

Overview

The 3D Card component adds smooth, perspective-driven depth and motion to any content block.
It reacts to mouse movement with subtle parallax and tilt, creating a premium, interactive feel — perfect for profile cards, feature highlights, or callouts.


Installation

Use the CLI to install the component automatically:

npx shadcn@latest add https://ui.sanjid.shop/r/3d-card.json
pnpm dlx shadcn@latest add https://ui.sanjid.shop/r/3d-card.json
yarn shadcn@latest add https://ui.sanjid.shop/r/3d-card.json
bunx --bun shadcn@latest add https://ui.sanjid.shop/r/3d-card.json

Install dependencies:

npm install motion
pnpm add motion
yarn add motion
bun add motion

Add the utility function for class merging:

lib/utils.ts
1import { ClassValue, clsx } from "clsx";2import { twMerge } from "tailwind-merge";3
4export function cn(...inputs: ClassValue[]) {5  return twMerge(clsx(inputs));6}

Copy the component code into your project:

components/ui/3d-card.tsx
1"use client";2
3import { motion, useMotionValue, useSpring, useTransform } from "framer-motion";4import clsx from "clsx";5import { ReactNode } from "react";6
7interface ThreeDTiltCardProps {8  children: ReactNode;9  className?: string;10  intensity?: number;11  depth?: number;12  border?: boolean;13}14
15export function ThreeDTiltCard({16  children,17  className,18  intensity = 0.45,19  depth = 80,20  border = true,21}: ThreeDTiltCardProps) {22  const x = useMotionValue(0);23  const y = useMotionValue(0);24  const z = useMotionValue(0);25
26  const springConfig = { stiffness: 120, damping: 18, mass: 0.6 };27  const springX = useSpring(x, springConfig);28  const springY = useSpring(y, springConfig);29  const springZ = useSpring(z, { stiffness: 100, damping: 15 });30
31  const rotateX = useTransform(32    springY,33    [-intensity, intensity],34    ["12deg", "-12deg"],35  );36  const rotateY = useTransform(37    springX,38    [-intensity, intensity],39    ["-12deg", "12deg"],40  );41
42  const childTranslateX = useTransform(43    springX,44    [-intensity, intensity],45    [depth * 0.2, -depth * 0.2],46  );47  const childTranslateY = useTransform(48    springY,49    [-intensity, intensity],50    [depth * 0.2, -depth * 0.2],51  );52
53  const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {54    const rect = e.currentTarget.getBoundingClientRect();55    const xPct = (e.clientX - rect.left) / rect.width - 0.5;56    const yPct = (e.clientY - rect.top) / rect.height - 0.5;57    x.set(xPct * 2);58    y.set(yPct * 2);59    z.set(depth);60  };61
62  const resetTilt = () => {63    x.set(0);64    y.set(0);65    z.set(0);66  };67
68  return (69    <motion.div70      onMouseMove={handleMouseMove}71      onMouseLeave={resetTilt}72      style={{73        rotateX,74        rotateY,75        transformStyle: "preserve-3d",76        perspective: 1200,77      }}78      whileHover={{ scale: 1.03 }}79      transition={{ type: "spring", stiffness: 160, damping: 18 }}80      className={clsx(81        "group relative rounded-2xl bg-neutral-50 dark:bg-neutral-900",82        "shadow-[0_6px_20px_rgba(0,0,0,0.08)] dark:shadow-[0_6px_20px_rgba(255,255,255,0.04)]",83        "transition-all duration-500 ease-[cubic-bezier(0.19,1,0.22,1)]",84        className,85      )}86    >87      {/* Ambient Glow */}88      <div89        className={clsx(90          "pointer-events-none absolute inset-px z-0 rounded-2xl blur-xl transition-opacity duration-700",91          "opacity-0 group-hover:opacity-100",92          "bg-linear-to-br from-neutral-100/50 via-neutral-200/20 to-neutral-300/10",93          "dark:from-neutral-800/40 dark:via-neutral-900/20 dark:to-neutral-950/10",94        )}95      />96
97      {/* Glass Reflection */}98      <motion.div99        style={{ rotateX, rotateY }}100        className="pointer-events-none absolute inset-0 z-10 rounded-2xl bg-linear-to-tl from-white/15 via-transparent to-transparent dark:from-white/5"101      />102
103      {/* Inner Card */}104      <div105        style={{ transformStyle: "preserve-3d" }}106        className={clsx(107          "relative z-20 h-full w-full rounded-2xl p-6 backdrop-blur-md",108          border &&109          "border border-neutral-200 shadow-inner dark:border-neutral-800",110        )}111      >112        <motion.div113          style={{114            translateX: childTranslateX,115            translateY: childTranslateY,116            translateZ: springZ,117            transformStyle: "preserve-3d",118          }}119          className="transition-transform duration-700 ease-[cubic-bezier(0.19,1,0.22,1)]"120        >121          {children}122        </motion.div>123      </div>124
125      {/* Subtle Inner Shadow (fixes double border glow) */}126      <div className="pointer-events-none absolute inset-0 -z-10 rounded-2xl shadow-[inset_0_0_35px_rgba(0,0,0,0.04)] dark:shadow-[inset_0_0_35px_rgba(255,255,255,0.03)]" />127    </motion.div>128  );129}

Adjust import paths according to your folder structure.


Props

PropTypeRequiredDefaultDescription
childrenReact.ReactNodeYesContent to be rendered inside the 3D card.
classNamestringNoOptional custom class names for styling.
intensitynumberNo0.4Controls how much the card tilts when hovered.
depthnumberNo60Determines the pop-out distance of the inner content.
borderbooleanNotrueToggles the border around the card.

Example

1

Accessibility

The 3D Card follows accessibility best practices:

  • Reduced Motion — Animations respect prefers-reduced-motion.

  • Keyboard Friendly — Fully accessible with tab navigation.

  • Screen Reader Support — Uses semantic HTML for inner content.

  • Focus Indicators — Maintains visible focus states even with transforms applied.


Performance Tips

Use Hardware Acceleration — CSS transforms automatically offload to the GPU.

  • Limit Depth — Too much z-distance can trigger motion sickness.

  • Optimize Renders — Memoize content inside the card to avoid re-renders.

  • Touch Devices — Consider disabling tilt on mobile or use gentle touch parallax.