Tilt

A spring-animated 3D tilt wrapper that tracks the cursor position inside the element.

Made by Léo
Open in

Installation

Install the following dependencies:

Copy and paste the following code into your project:

components/unlumen-ui/tilt.tsx
"use client";

import * as React from "react";
import {
  motion,
  useMotionTemplate,
  useMotionValue,
  useSpring,
  useTransform,
  type MotionStyle,
  type SpringOptions,
} from "motion/react";

export type TiltProps = {
  children: React.ReactNode;
  className?: string;
  style?: MotionStyle;
  /**
   * Maximum rotation angle in degrees.
   * @default 15
   */
  rotationFactor?: number;
  /**
   * Reverse the tilt direction.
   * @default false
   */
  isReverse?: boolean;
  springOptions?: SpringOptions;
};

export function Tilt({
  children,
  className,
  style,
  rotationFactor = 15,
  isReverse = false,
  springOptions,
}: TiltProps) {
  const ref = React.useRef<HTMLDivElement>(null);

  const x = useMotionValue(0);
  const y = useMotionValue(0);

  const xSpring = useSpring(x, springOptions);
  const ySpring = useSpring(y, springOptions);

  const rotateX = useTransform(
    ySpring,
    [-0.5, 0.5],
    isReverse
      ? [rotationFactor, -rotationFactor]
      : [-rotationFactor, rotationFactor],
  );
  const rotateY = useTransform(
    xSpring,
    [-0.5, 0.5],
    isReverse
      ? [-rotationFactor, rotationFactor]
      : [rotationFactor, -rotationFactor],
  );

  const transform = useMotionTemplate`perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`;

  const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
    if (!ref.current) return;
    const rect = ref.current.getBoundingClientRect();
    x.set((e.clientX - rect.left) / rect.width - 0.5);
    y.set((e.clientY - rect.top) / rect.height - 0.5);
  };

  const handleMouseLeave = () => {
    x.set(0);
    y.set(0);
  };

  return (
    <motion.div
      ref={ref}
      className={className}
      style={{ transformStyle: "preserve-3d", ...style, transform }}
      onMouseMove={handleMouseMove}
      onMouseLeave={handleMouseLeave}
    >
      {children}
    </motion.div>
  );
}

Update the import paths to match your project setup.

Usage

import { Tilt } from "@/components/unlumen-ui/tilt";

<Tilt rotationFactor={15}>
  <div className="rounded-xl border bg-card p-6">Hover me</div>
</Tilt>;

Props

PropTypeDefaultDescription
rotationFactornumber15Maximum rotation in degrees
isReversebooleanfalseReverse the tilt direction
springOptionsSpringOptionsForwarded to useSpring (stiffness, damping, …)
classNamestringClass applied to the motion wrapper
styleMotionStyleStyle applied to the motion wrapper

Built by Léo from Unlumen :3

Last updated: 3/15/2026