Tilt Card
spell-ui/tilt-cardFreeComponent
Interactive 3D tilt card with perspective hover effect and spotlight.
Live preview
live · rendered by the registry
Rendered by spell-ui on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://spell.sh/r/tilt-card.jsonSource
1"use client"23import { useRef, useState, useCallback } from "react"4import { cn } from "@/lib/utils"56export interface TiltCardProps {7 /** Maximum tilt angle in degrees */8 tiltLimit?: number9 /** Scale factor on hover */10 scale?: number11 /** Perspective distance in pixels */12 perspective?: number13 /** Tilt direction: "gravitate" follows cursor, "evade" tilts away */14 effect?: "gravitate" | "evade"15 /** Show a spotlight that follows the cursor on hover */16 spotlight?: boolean17 /** Additional class name */18 className?: string19 /** Additional inline styles */20 style?: React.CSSProperties21 /** Card content */22 children?: React.ReactNode23}2425export function TiltCard({26 tiltLimit = 15,27 scale = 1.05,28 perspective = 1200,29 effect = "evade",30 spotlight = true,31 className,32 style,33 children,34}: TiltCardProps) {35 const cardRef = useRef<HTMLDivElement>(null)36 const [transform, setTransform] = useState(37 `perspective(${perspective}px) rotateX(0deg) rotateY(0deg) scale3d(1, 1, 1)`38 )39 const [spotlightPos, setSpotlightPos] = useState({ x: 50, y: 50 })40 const [isHovered, setIsHovered] = useState(false)4142 const dir = effect === "evade" ? -1 : 14344 const handlePointerMove = useCallback(45 (e: React.PointerEvent) => {46 const el = cardRef.current47 if (!el) return48 const rect = el.getBoundingClientRect()49 const px = (e.clientX - rect.left) / rect.width50 const py = (e.clientY - rect.top) / rect.height51 const xRot = (py - 0.5) * (tiltLimit * 2) * dir52 const yRot = (px - 0.5) * -(tiltLimit * 2) * dir53 setTransform(54 `perspective(${perspective}px) rotateX(${xRot}deg) rotateY(${yRot}deg) scale3d(${scale}, ${scale}, ${scale})`55 )56 if (spotlight) {57 setSpotlightPos({ x: px * 100, y: py * 100 })58 }59 },60 [tiltLimit, scale, perspective, dir, spotlight]61 )6263 const handlePointerEnter = useCallback(() => {64 setIsHovered(true)65 }, [])6667 const handlePointerLeave = useCallback(() => {68 setTransform(69 `perspective(${perspective}px) rotateX(0deg) rotateY(0deg) scale3d(1, 1, 1)`70 )71 setIsHovered(false)72 }, [perspective])7374 return (75 <div76 ref={cardRef}77 onPointerEnter={handlePointerEnter}78 onPointerMove={handlePointerMove}79 onPointerLeave={handlePointerLeave}80 className={cn("will-change-transform relative overflow-hidden", className)}81 style={{82 transform,83 transition: "transform 0.2s ease-out",84// … truncated
Files it writes
More from spell-ui
All 33 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Animated Checkboxanimated-checkbox | spell-ui | Components | Free | 1 dep | |
| Animated Gradientanimated-gradient | spell-ui | Components | Free | no deps | |
| Badgebadge | spell-ui | Components | Free | 2 deps | |
| Bars Spinnerbars-spinner | spell-ui | Components | Free | no deps | |
| Blur Revealblur-reveal | spell-ui | Components | Free | 1 dep | |
| Chartchart | spell-ui | Components | Free | no deps | |
| Color Selectorcolor-selector | spell-ui | Components | Free | no deps | |
| Copy Buttoncopy-button | spell-ui | Components | Free | 1 dep |
