← Back to gallery
CSSFeatured

Animated 3D Cube Perspective Walkthrough

Six-face CSS 3D cube that separates scene perspective from per-face transform-order so the rotate → translateZ relationship stays inspectable. Three teaching variants: a balanced centred scene, a wide-origin camera bias, and a slow face-assembly view.

css-3dperspectivepreserve-3dtranslateZtransform-orderperspective-originprefers-reduced-motion

CSS 3D / Perspective

Animated 3D Cube Perspective Walkthrough

A preserve-3d cube shows how scene perspective, face rotation, translateZ, and transform order produce real depth.

Center origin

Balanced Scene

A six-face cube rotates slowly inside a centered perspective scene. Each face rotates first, then moves out by half the cube size with translateZ.

  • perspective
  • preserve-3d
  • translateZ

Offset perspective

Wide Origin

A shifted perspective-origin makes depth bias visible without changing face placement. Use origin changes to teach camera position.

  • perspective-origin
  • camera bias
  • css-3d

Structure-first view

Face Assembly

A quieter cube highlights visible face separation and transform responsibility. Keep labels and face colors simple when demonstrating transform order.

  • face-rotate
  • transform-order
  • teaching

Cube inspector

Balanced Scene

  • perspective
  • preserve-3d
  • translateZ

A six-face cube rotates slowly inside a centered perspective scene. Each face rotates first, then moves out by half the cube size with translateZ.

Helped you ship something? 🐟 Send my cat a churu

.scene {
  perspective: 720px;
  perspective-origin: 50% 42%;
}

.cube {
  transform-style: preserve-3d;
  animation: cubeTurn 7.00s linear infinite;
}

.cube__face--front { transform: rotateY(0deg)   translateZ(48px); }
.cube__face--back  { transform: rotateY(180deg) translateZ(48px); }
.cube__face--right { transform: rotateY(90deg)  translateZ(48px); }
.cube__face--left  { transform: rotateY(-90deg) translateZ(48px); }
.cube__face--top   { transform: rotateX(90deg)  translateZ(48px); }
.cube__face--bottom{ transform: rotateX(-90deg) translateZ(48px); }

@keyframes cubeTurn {
  0%   { transform: rotateX(-20deg) rotateY(22deg); }
  100% { transform: rotateX(-20deg) rotateY(382deg); }
}

@media (prefers-reduced-motion: reduce) {
  .cube { animation: none; transform: rotateX(-18deg) rotateY(28deg); }
}

How to make this

A CSS 3D cube uses perspective on a parent scene, transform-style: preserve-3d on the cube, and six faces rotated first then pushed out with translateZ half the side length.

html
1<div class="cube-scene" aria-hidden="true">  <div class="cube">    <span class="cube__face cube__face--front">Front</span>    <span class="cube__face cube__face--back">Back</span>    <span class="cube__face cube__face--right">Right</span>    <span class="cube__face cube__face--left">Left</span>    <span class="cube__face cube__face--top">Top</span>    <span class="cube__face cube__face--bottom">Bottom</span>  </div></div> <style>.cube-scene {  display: grid;  place-items: center;  width: 220px;  height: 180px;  perspective: 720px;  perspective-origin: 50% 42%;20  background: radial-gradient(circle, #1e293b, #020617);}.cube {  position: relative;  width: 88px;  height: 88px;26  transform-style: preserve-3d;  animation: cube-perspective-turn 7s linear infinite;}.cube__face {  position: absolute;  inset: 0;  display: grid;  place-items: center;  border: 1px solid rgba(226, 232, 240, .28);  background: rgba(15, 23, 42, .82);  color: white;  font: 800 .72rem/1 sans-serif;}.cube__face--front { transform: rotateY(0deg) translateZ(44px); }.cube__face--back { transform: rotateY(180deg) translateZ(44px); }41.cube__face--right { transform: rotateY(90deg) translateZ(44px); }.cube__face--left { transform: rotateY(-90deg) translateZ(44px); }.cube__face--top { transform: rotateX(90deg) translateZ(44px); }.cube__face--bottom { transform: rotateX(-90deg) translateZ(44px); }@keyframes cube-perspective-turn {  from { transform: rotateX(-20deg) rotateY(22deg); }47  to { transform: rotateX(-20deg) rotateY(382deg); }}@media (prefers-reduced-motion: reduce) {  .cube {51    animation: none;    transform: rotateX(-18deg) rotateY(28deg);  }}</style>

Annotated snippet

  1. Line 1The cube is decorative in this snippet, so it is hidden from assistive technology. If a 3D object represents product state, expose the state as text or controls outside the rotating object.
    PitfallWhere should perspective go for a CSS 3D cube?

    Put perspective on a parent scene and rotate the cube inside it. The parent acts like the camera. If the cube itself owns perspective, nested faces and sibling objects become harder to reason about and the depth can feel inconsistent.

  2. Line 20perspective belongs on the parent scene, not the rotating cube. This makes the scene act like the camera while the cube remains the object being transformed.
    PitfallWhere should perspective go for a CSS 3D cube?

    Put perspective on a parent scene and rotate the cube inside it. The parent acts like the camera. If the cube itself owns perspective, nested faces and sibling objects become harder to reason about and the depth can feel inconsistent.

  3. Line 26transform-style: preserve-3d keeps the six faces in a shared 3D space. Without it, child transforms flatten before the cube rotation is applied.

    Same scene (perspective: 420px) with the same two coloured faces, same rotateY(90deg) translateZ(37px) on the magenta side face, same parent tilt. Only transform-style on the cube differs. The default flat behaviour resolves every child transform inside the cube box before applying the parent rotation — the side face collapses onto the cyan front and reads as one squashed quad. preserve-3d keeps each face in shared 3D space, so the side face stays where translateZ put it and the cube reads as a cube.

    PitfallWhy does my CSS cube look flat?

    The cube wrapper is probably missing transform-style: preserve-3d, or a parent is flattening the 3D context. Keep preserve-3d on the object that owns the faces, and avoid transform or overflow choices on ancestors that unintentionally flatten or clip the scene.

  4. Line 41Each face rotates into its side orientation, then translateZ moves it out by half the cube size. Reversing the transform order changes where the face lands.

    Same cube setup (preserve-3d, perspective on scene, parent tilt, four side faces with the same rotation angles 0/90/180/-90). Only the order of transforms on each face differs. rotateY(angle) translateZ(37px) rotates the face into its side orientation first, then pushes it out along the rotated local Z axis — the four faces meet edge to edge and form the cube. translateZ(37px) rotateY(angle) pushes every face out 37px along the same shared Z first, then rotates each in place — the four faces orbit the cube center like flat panels and never close into a solid shape.

    PitfallDoes transform order matter for cube faces?

    Yes. A face should rotate into the correct side orientation and then translateZ by half the cube size. Changing the function order changes the coordinate system used for the translation, so faces can orbit around the cube instead of forming its sides.

  5. Line 47The keyframe keeps the X angle fixed and rotates around Y. That makes the cube readable while still showing every side over the cycle.
    PitfallAre animated CSS 3D cubes expensive?

    A small transform-only cube is usually cheap, but large translucent faces, heavy shadows, filters, and many simultaneous cubes can stress compositing. Keep the cube bounded, animate transform only, and avoid putting complex DOM inside every face.

  6. Line 51Reduced motion pauses the loop at a legible angle. Do not collapse the cube to a flat front face, because the static 3D structure is still the lesson.
    PitfallHow should a rotating cube respect prefers-reduced-motion?

    Pause the animation and leave the cube at a readable angle. Users who reduce motion should still see the 3D construction, just without continuous rotation. If the cube is interactive, keep keyboard and pointer controls available.

Advanced

Animate perspective-origin to orbit the viewing camera around the cube

Same scene, same perspective: 420px, same cube spinning on rotateY at 4.8s. BEFORE keeps perspective-origin frozen at 50% 50% — the camera stays bolted in one spot, so the cube reads as an object rotating on a turntable seen from a single angle. AFTER animates perspective-origin on a 4.8s loop synchronized with the rotation; the vantage point orbits between the four corners of the scene, so the cube appears inspected from above, the side, and below in turn — a true walkthrough rather than a fixed-angle spin. Both demos finish a full cycle in lockstep.

View explanation and full code18 lines

The base recipe sets perspective-origin to a static point (50% 42%) — the camera stays in one spot while the cube spins on its own axis. Real product walkthrough scenes also move the camera: the viewing vantage drifts across the scene over a longer cycle so the cube appears to be inspected from multiple sides instead of rotating in place. Animate perspective-origin in a slow ease-in-out loop on the same parent .cube-scene the base already uses — the cube's own rotation keyframe stays untouched and runs underneath. Same property the base teaches, just extended from a static value to keyframe interpolation; the layered "camera orbit" is what makes the slug's name (walkthrough) earn itself.

Append these rules inside the <style> block from the base snippet above.

css
/* Advanced: animated perspective-origin — extends the base recipe.   The base sets perspective-origin to a fixed point. Animating it on   the same .cube-scene parent sweeps the viewer's vantage in a small   orbit around the cube, layering a "walking around" sensation onto   the cube's own rotation. Two independent animations on two different   elements; both keep the same shared 3D scene. */.cube-scene {  animation: cube-perspective-camera-orbit 12s ease-in-out infinite;}@keyframes cube-perspective-camera-orbit {  0%, 100% { perspective-origin: 30% 36%; }  25%      { perspective-origin: 68% 30%; }  50%      { perspective-origin: 72% 60%; }  75%      { perspective-origin: 32% 58%; }}@media (prefers-reduced-motion: reduce) {  .cube-scene { animation: none; perspective-origin: 50% 42%; }}

Notes

Overview

CSS 3D cubes are the canonical teaching example for the difference between scene perspective and element transforms. The scene owns perspective (the camera distance); the cube owns transform-style: preserve-3d (so children render in shared 3D space); each face owns its own transform: rotateX/Y + translateZ(half-side) composition. Get one of those three responsibilities wrong and the cube collapses to a flat square.

When to use it

Reach for a 3D cube as a teaching demo, a navigation primitive (carousel of six panels), or a logo treatment for a product that wants depth without modeling tools. Skip it for production content where each face would carry meaningful text — backface-visible faces are hard to keep accessible, and the rotation makes reading content nearly impossible mid-spin.

How it works

Set perspective: 720px on the scene wrapper; smaller values exaggerate the perspective (fisheye-ish), larger values flatten it (telephoto-ish). Set transform-style: preserve-3d on the cube itself so the six face elements render in shared 3D space rather than each being flattened. Each face: first rotate to its target axis (e.g. rotateY(90deg) for the right face), then translateZ(half the cube side) to push the face out from the center. Transform order matters — rotate first, then translateZ, never the reverse.

Production gotchas

perspective-origin defaults to 50% 50%— the camera sits at the cube’s center. Move the origin (e.g. perspective-origin: 50% 30%) to bias the camera up, which is what makes a cube look like you are looking slightly down on it instead of dead-on. Without backface-visibility hidden, the back faces show through translucent fronts and the cube looks broken; set backface-visibility: hidden on every face. If your cube is rotating and one face flickers in/out of view, you have a z-fighting issue — add translateZ(0.01px) to the back face so it never sits at exactly the same depth as the front.

Accessibility

A spinning cube under prefers-reduced-motion: reduce should freeze at a readable angle — not the default rest position (which is usually a face-on flat view), but a slight rotation that still shows the cube IS a cube. Face content should be real DOM text, not images, so that backface-hidden faces are still indexed and screen-reader-accessible during pauses.

References

Implementation depth

A CSS cube only works when scene and faces have separate responsibilities. The scene owns perspective, the cube owns transform-style: preserve-3d, and each face owns rotate plus translateZ in the correct order.

Use it for teaching or decorative navigation, not for dense reading content. Backface visibility, z-fighting, and perspective-origin changes can all affect legibility, so reduced motion should freeze at an angle that still communicates depth.