Tilt Card
loomui/tilt-cardFreeComponent
A surface that leans away from the pointer in 3D and settles back on leave.
Live preview
live · rendered by the registry
Rendered by loomui on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://loomui.design/r/tilt-card.jsonSource
1"use client"23import * as React from "react"45import { cn } from "@/lib/utils"67export interface TiltCardProps extends React.ComponentProps<"div"> {8 /** Largest tilt in degrees, reached at the corners. */9 max?: number10 /** Depth of the perspective, as a CSS length. Lower is more extreme. */11 perspective?: string12 /** Scale held while the pointer is over the card. */13 scale?: number14 /** Lay a sheen over the card that follows the pointer. */15 glare?: boolean16 /** Render the card flat. */17 disabled?: boolean18}1920export function TiltCard({21 children,22 className,23 max = 10,24 perspective = "800px",25 scale = 1.02,26 glare = true,27 disabled = false,28 style,29 ...props30}: TiltCardProps) {31 const ref = React.useRef<HTMLDivElement>(null)32 const frame = React.useRef(0)33 const [reduced, setReduced] = React.useState(false)3435 React.useEffect(() => {36 const query = window.matchMedia("(prefers-reduced-motion: reduce)")37 setReduced(query.matches)3839 const onChange = (event: MediaQueryListEvent) => setReduced(event.matches)40 query.addEventListener("change", onChange)41 return () => query.removeEventListener("change", onChange)42 }, [])4344 // A tilt is the whole component, so there is nothing to soften for someone45 // who asked for less motion. The card is rendered flat instead.46 const flat = disabled || reduced4748 const write = React.useCallback((x: number, y: number, active: number) => {49 const node = ref.current50 if (!node) {51 return52 }53 node.style.setProperty("--tilt-x", `${x}`)54 node.style.setProperty("--tilt-y", `${y}`)55 node.style.setProperty("--tilt-active", `${active}`)56 }, [])5758 const reset = React.useCallback(() => {59 cancelAnimationFrame(frame.current)60 write(50, 50, 0)61 }, [write])6263 // Pointer position lands on custom properties inside a frame, so a move64 // costs a compositor update rather than a React render.65 const handlePointerMove = React.useCallback(66 (event: React.PointerEvent<HTMLDivElement>) => {67 if (flat) {68 return69 }70 const { clientX, clientY } = event71 cancelAnimationFrame(frame.current)72 frame.current = requestAnimationFrame(() => {73 const node = ref.current74 if (!node) {75 return76 }77 const rect = node.getBoundingClientRect()78 write(79 ((clientX - rect.left) / rect.width) * 100,80 ((clientY - rect.top) / rect.height) * 100,81 182 )83 })84 },85 [flat, write]86 )8788// … truncated
What it pulls in
Other registry items
Files it writes
More from loomui
All 91 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Aurora Backdropaurora-backdrop | loomui | Components | Free | no deps | |
| Bento Gridbento-grid | loomui | Components | Free | no deps | |
| Card Stackcard-stack | loomui | Components | Free | no deps | |
| Compare Slidercompare-slider | loomui | Components | Free | no deps | |
| Confetti Buttonconfetti-button | loomui | Components | Free | no deps | |
| Count Upcount-up | loomui | Components | Free | no deps | |
| Credit Cardcredit-card | loomui | Components | Free | no deps | |
| Drawerdrawer | loomui | Components | Free | 1 dep |
