Retour au catalogue

Hero 3D Tilt

Hero avec carte 3D qui s'incline au survol de la souris. Effet perspective immersif.

heromedium Both Responsive a11y
boldplayfulsaasagencycentered
Theme

How to build a 3D tilt hero section in React

A 3D tilt hero in React maps the pointer position inside a card to rotateX and rotateY motion values, wraps them in Framer Motion springs (stiffness 200, damping 30), and applies them to a motion.div with transformStyle:'preserve-3d' and a parent perspective of 1200px. The card tilts up to 8 degrees on each axis, then eases back to flat when the cursor leaves.

  • Stack: React 18 + Framer Motion 11 + Tailwind v4 + lucide-react, ~175 lines.
  • Core API: useMotionValue, useSpring, useTransform, no extra dependencies beyond framer-motion.
  • Tilt range is ±8 degrees on both axes; spring settings can be tuned per brand feel.
  • Accessible: content is standard HTML with no hidden layers; the tilt is decorative and does not affect tab order.
  • On touch devices the card stays flat, no tilt fires without a mouse pointer.

Hero 3D Tilt is a centered hero section built around a single floating card that rotates in three dimensions as the cursor moves across it. The tilt follows the pointer with spring physics, not a rigid linear mapping, so the response feels weighty and natural. It works for SaaS products and agency sites that want a tactile first impression without resorting to full-screen video or complex WebGL.

Anatomy

The outer section sets a CSS perspective of 1200px on the viewport, giving the 3D scene its depth. Inside sits a centered container, and inside that a single motion.div (the card) with transformStyle:'preserve-3d', this is where rotateX and rotateY are applied. The card contains three visual layers stacked via z-index: a glass background panel (border + background-alt fill), a blurred accent glow radial gradient at the top, and the actual content (optional badge, h1 with an italic accent word, description, and a pill CTA button). All colors reference CSS custom property tokens so the card adapts to any theme preset.

How it works

The technique relies on three Framer Motion hooks chained together. useMotionValue holds mouseX and mouseY as normalized floats in the range [-0.5, 0.5], derived from the cursor's position relative to the card's bounding box. useTransform converts each value to degrees: mouseY maps to rotateX (front tilts up when cursor is near the top) and mouseX maps to rotateY (left edge lifts when cursor moves right). Both derived values then pass through useSpring with stiffness 200 and damping 30, adding inertia so the card settles rather than snapping. On mouseleave, both motion values reset to 0, and the spring smoothly returns the card to its resting flat position.

How to build it in React

  1. Set the perspective on the parent

    CSS perspective must live on a parent element, not on the element that rotates. Add perspective:'1200px' as an inline style to the outer section. A value between 800px and 1500px gives a convincing depth without over-distorting the card at maximum tilt.

    <section style={{ perspective: "1200px" }}>
      <motion.div style={{ rotateX, rotateY, transformStyle: "preserve-3d" }}>
        {/* card content */}
      </motion.div>
    </section>
  2. Track the pointer as normalized coordinates

    On each mousemove event, read getBoundingClientRect from the card ref, then divide the cursor offset by the card width and height. Subtracting 0.5 centers the range at zero, so the card sits flat when the cursor is in the middle and tilts at the edges.

    function handleMouseMove(e: React.MouseEvent<HTMLDivElement>) {
      const rect = cardRef.current?.getBoundingClientRect();
      if (!rect) return;
      const x = (e.clientX - rect.left) / rect.width - 0.5;
      const y = (e.clientY - rect.top) / rect.height - 0.5;
      mouseX.set(x);
      mouseY.set(y);
    }
  3. Map coordinates to spring-damped rotation

    Pass mouseX and mouseY through useTransform to scale them to a degree range, then wrap each result in useSpring. The stiffness controls how quickly the card chases the pointer; damping controls how much it overshoots before settling. A stiffness of 200 with damping 30 feels responsive without being jittery.

    const rotateX = useSpring(useTransform(mouseY, [-0.5, 0.5], [8, -8]), {
      stiffness: 200,
      damping: 30,
    });
    const rotateY = useSpring(useTransform(mouseX, [-0.5, 0.5], [-8, 8]), {
      stiffness: 200,
      damping: 30,
    });
  4. Reset on mouse leave

    Set both mouseX and mouseY back to 0 in the onMouseLeave handler. Because the derived rotateX/rotateY values are wrapped in springs, the card eases back to flat automatically, no manual animation call needed.

    function handleMouseLeave() {
      mouseX.set(0);
      mouseY.set(0);
    }

When to use it

Reach for this component when you need a hero that communicates craft and interactivity within the first three seconds, SaaS product launches, agency portfolios, and design tool landing pages are the natural fit. The single-card layout keeps focus tight, so it works best when the value proposition fits in one headline and one short paragraph. Skip it on e-commerce product pages where the hero competes with a product image, on forms-first pages like sign-up flows, and on any page where the primary audience is mobile-only users: the tilt never fires without a mouse, so you would be shipping complexity for no effect.

Used by

  • Stripe, Uses subtle 3D card tilt on product feature cards to give depth to its marketing site.
  • Linear, Applies perspective-tilted UI mockups in hero sections to make product screenshots feel three-dimensional.
  • Vercel, Employs 3D depth and spring-based hover responses on feature showcase cards throughout its homepage.
  • Framer, Showcases 3D tilt interactions directly in its own marketing as a live demo of what the tool enables.

FAQ

Why does the tilt not work on mobile?

The effect relies on mousemove events, which do not fire on touch screens. The card renders flat on mobile and still displays all content. If you need a touch equivalent, you can wire up deviceorientation events or a gyroscope API, though that requires explicit permission on iOS 13+.

Can I increase the tilt angle beyond 8 degrees?

Yes. Change the output range in the useTransform calls from [8, -8] to whatever fits your design. Beyond 15-20 degrees the distortion becomes hard to read, and text legibility drops sharply. Keep the perspective value proportional, a smaller perspective (e.g. 600px) amplifies the distortion at the same angle.

How do I tune the spring feel to be snappier or bouncier?

Raise stiffness to make the card chase the cursor faster; lower damping to add overshoot and bounce. A stiffness of 400 with damping 20 gives a snappy, bouncy feel. A stiffness of 80 with damping 25 produces a slow, laggy drag. The defaults (200/30) sit in the middle and work for most product contexts.

Does transformStyle: preserve-3d have any browser compatibility issues?

It is supported in all modern browsers. The only edge case is Safari on older iOS versions (pre-15), where preserve-3d can be ignored inside certain stacking contexts. Test with overflow:hidden and z-index if you see the 3D effect collapsing on Safari.

"use client";

import { motion, useMotionValue, useSpring, useTransform } from "framer-motion";
import { ArrowRight } from "lucide-react";
import { useRef } from "react";

interface Hero3dProps {
  title?: string;
  titleAccent?: string;
  description?: string;
  ctaLabel?: string;
  ctaUrl?: string;
  badge?: string;
}

const EASE = [0.16, 1, 0.3, 1] as const;

export default function Hero3d({
  title = "Votre titre principal",
  titleAccent = "innovant",
  description = "Description du hero",
  ctaLabel = "Commencer",

Code complet réservé à Pro

Code source intégral, export multi-framework et playground.

Passer en Pro, 9,99€/mois

Reviews

React 3D Tilt Hero with Framer Motion, Code + Tutorial