Cards & Grids

Tilt Card

A 3D perspective tilt card — the card tilts with the cursor, a glare sweeps across its surface, it springs back on leave, and content can float up with translateZ.

Bring this into your project

Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.

npx webberui@latest add tilt-card.en

New project or installation trouble? Start here →

Tilt Card is a React 3D tilt card component built with Motion and Tailwind CSS. It uses CSS perspective with rotateX/rotateY for depth, and TiltCardItem lets a title or button float above the card face.

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

12
1.02
Active edited usageDefault props
<TiltCard/>

The complete example includes the preview layout and data. Install the component first, then paste this file into your project.

When to use

  • Product or project cards in a store or portfolio
  • Plan cards on a pricing page, so the hovered plan stands out
  • Showcases that should feel physical, such as membership cards, tickets, or badges
  • Character or prize cards on gaming and event pages
  • Not a good fit: pages used mostly on phones, since it doesn't tilt on touch. For a rainbow foil collectible-card look, see Holographic Card

Installation

npx webberui@latest add https://webberui.com/r/tilt-card.en.json

Installing also writes the --wb-duration-fast CSS variable into your global stylesheet.

Usage

import { TiltCard, TiltCardItem } from "@/components/ui/tilt-card";

<TiltCard maxTilt={12} scale={1.02}>
  <div className="h-40 rounded-t-xl bg-neutral-100 dark:bg-neutral-800" />
  <TiltCardItem z={40} className="p-5">
    <h3 className="font-semibold">Title</h3>
    <p className="text-sm text-neutral-500">This content floats 40px above the card face.</p>
  </TiltCardItem>
</TiltCard>

Examples

Product card with Next.js Image

The card body must not use overflow-hidden, so round the corners on the full-bleed image itself.

import Image from "next/image";
import { TiltCard, TiltCardItem } from "@/components/ui/tilt-card";

<TiltCard className="w-72">
  <Image
    src="/products/headphones.jpg"
    alt="Wireless noise-cancelling headphones"
    width={288}
    height={192}
    className="h-48 w-full rounded-t-xl object-cover"
  />
  <TiltCardItem z={30} className="space-y-1 p-5">
    <h3 className="font-semibold">Wireless noise-cancelling headphones</h3>
    <p className="text-sm text-neutral-500">30-hour battery, multipoint pairing</p>
  </TiltCardItem>
</TiltCard>

Custom glare colour

The glare reads the --wb-tilt-glare CSS variable, which you can set on TiltCard through style.

<TiltCard
  className="w-64 bg-neutral-950 p-6 text-white"
  style={{ "--wb-tilt-glare": "rgba(56, 189, 248, 0.35)" } as React.CSSProperties}
>
  <TiltCardItem z={50}>
    <p className="text-xs uppercase tracking-widest text-sky-300">Member</p>
    <p className="mt-2 text-2xl font-semibold">Annual membership</p>
  </TiltCardItem>
</TiltCard>

Subtle plan cards

For pricing cards side by side, lower maxTilt, set scale to 1, and turn off glare for a more restrained effect.

<div className="grid gap-6 sm:grid-cols-3">
  {plans.map((plan) => (
    <TiltCard key={plan.id} maxTilt={6} scale={1} glare={false} className="p-6">
      <h3 className="font-semibold">{plan.name}</h3>
      <p className="mt-2 text-sm text-neutral-500">{plan.summary}</p>
    </TiltCard>
  ))}
</div>

Props

TiltCard

PropTypeDefaultDescription
maxTiltnumber12Maximum tilt angle (degrees); rotateX / rotateY reach this angle when the cursor is at the card's edge
glarebooleantrueWhether to show the glare layer that follows the cursor
scalenumber1.02Scale factor on hover; 1 means no scaling
springConfigSpringOptions{ stiffness: 300, damping: 25, mass: 0.5 }Spring parameters shared by the tilt, scale, and spring back
classNamestring—Styles for the card body (a neutral rounded card by default, overridable; do not add overflow-hidden)
childrenReactNode—Card content

Remaining <div> attributes (onClick, aria-*, and so on) are forwarded to the outer perspective container.

TiltCardItem

PropTypeDefaultDescription
znumber40Height the content floats toward the viewer along the Z axis (px)
classNamestring—Custom styles
childrenReactNode—The content to float

How it works

  • 3D perspective: the outer container is fixed at perspective: 1000px and the card body uses transform-style: preserve-3d; rotateX / rotateY map linearly to ±maxTilt based on the cursor's position relative to the center
  • Glare: a white radial-gradient highlight follows the cursor position, growing stronger the further the cursor is from the center (0 when dead-center, brighter the more the card tilts); the highlight color can be overridden with the --wb-tilt-glare CSS variable (default rgba(255, 255, 255, 0.4))
  • Spring back: every transform is driven by useMotionValue + useSpring, so once the cursor leaves the card springs back with momentum and the glare fades out
  • Floating layers: TiltCardItem uses translateZ to lift content off the card face; it must be a direct child of TiltCard, and if another container sits in between, give that container transform-style: preserve-3d
  • Avoid overflow-hidden: setting overflow-hidden on the card body flattens the 3D context (translateZ stops working), so for full-bleed images add the radius to the image itself instead (for example rounded-t-xl)
  • Stable, no jitter: pointer events are attached to the untransformed outer container, so the card never reads back its own transformed bounds mid-rotation and feeds jitter into the loop
  • Automatically disabled on touch devices: only mouse pointer events are handled (pointerType === "mouse"), so on touch it is just an ordinary card
  • When the user has "reduce motion" enabled at the system level, the tilt and glare are disabled; the card stays static and its content and functionality are unaffected

FAQ

Does it work with the Next.js App Router?

Yes. The file already starts with "use client", so you can import it from a Server Component page as usual.

Why isn't TiltCardItem floating?

TiltCardItem needs to be a direct child of TiltCard. If there's a wrapper in between, give that wrapper transform-style: preserve-3d (in Tailwind, [transform-style:preserve-3d]). Adding overflow-hidden to the card body also flattens the 3D.

Does it work on phones?

No. The component only responds to pointer events whose pointerType is mouse; on touch devices it renders as a regular card, and its content and clicks are unaffected.

How do I tune the tilt and feel?

maxTilt sets the maximum angle when the cursor reaches the edge, scale sets the hover zoom, and springConfig takes Motion's SpringOptions to adjust how fast and springy the return is.

What are the dependencies?

motion, clsx, and tailwind-merge. If you'd rather skip the CLI, the Manual tab in the installation section walks you through copying the source.