Skip to contact form
← BlogEngineeringJanuary 3, 2027 · 4 min read

A CSS 3D scene that tilts toward your cursor

How our Vision Pro page fakes headset parallax with rotateX/rotateY and a handful of translateZ values — real CSS 3D, no library. Move your cursor over the demo below.

creenshots can't show what a Vision Pro app feels like, because the entire point of the platform is that content sits at different depths in physical space around you — move your head and closer things shift more than far things, the way real objects do. Our Vision Pro service page fakes a version of that on a flat screen: move your pointer over the hero and the whole scene tilts, with UI cards floating at different simulated depths, using nothing but CSS 3D transforms.

transform-style:preserve-3dturnsaflatstackofdivsintoarigidobjectrotatetheparent,andeverychildkeepsitspositionin3Dspacerelativetotheothers,notjustvisuallylayeredontop.

Move your cursor over this

move your cursor

Pointer position inside the container maps to two rotation values — how far left/right you are becomes rotateY, how far up/down becomes rotateX — both run through a spring so the tilt settles rather than snapping to the cursor. The container carries perspective on its parent and transform-style: preserve-3d on itself; without that second property, child elements with their own translateZ would just render flat, because the browser wouldn't treat them as sharing one 3D space with their parent's rotation.

tsx

<div style={{ perspective: '1000px' }}> <motion.div style={{ rotateX, rotateY, transformStyle: 'preserve-3d' }}> <div style={{ transform: 'translate3d(-90px, -50px, -80px)' }}> {/* a card sitting further away */} </div> <div style={{ transform: 'translateZ(50px)' }}> {/* the near card, closest to the viewer */} </div> </motion.div> </div>

Why depth instead of just parallax scroll speeds

A cheaper version of "depth" just moves background layers slower than foreground layers on scroll — real parallax, but along one axis, and it doesn't respond to anything but scroll position. This scene responds to pointer position on two axes simultaneously, and because it's genuine 3D transforms rather than simulated offsets, every element's perceived movement is mathematically consistent with its actual translateZ — a card 80px "deeper" moves a specific, correct amount less than a card at 50px, the same way it would if you were actually looking at a physical diorama and shifted your head.

Why it's the one hero with no scroll-pinned sequence

Every other platform page on the site (web, iOS) opens with a scroll-driven sequence — the sheet stack, the fly-through words. Vision Pro's hero doesn't scroll-pin at all; it responds to the pointer sitting still and moving around inside a fixed viewport. That's deliberate, not an oversight: a headset doesn't have a scrollbar, and the platform's whole interaction model is about your position and gaze in a space rather than a linear feed you move through. The hero's mechanic matches the platform's actual interaction language instead of reusing the site's own default pattern out of habit.