webdevpuneet.com

3D Rotating Cube Loader — Real CSS 3D Cube with Six True Faces

3D Rotating Cube Loader · Loaders · Plain HTML & CSS · Live preview

CategoryLoaders

Copy & paste

Source Code

<div class="cb-stage">
  <div class="cb-scene">
    <div class="cb-cube" id="cbCube">
      <div class="cb-face cb-front">1</div>
      <div class="cb-face cb-back">2</div>
      <div class="cb-face cb-right">3</div>
      <div class="cb-face cb-left">4</div>
      <div class="cb-face cb-top">5</div>
      <div class="cb-face cb-bottom">6</div>
    </div>
  </div>
  <p class="cb-label">Loading</p>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Six real 3D faces
Actual positioned elements forming a closed cube, not a flat square.
Correct rotate-then-translate math
Each face rotates first, then pushes out along its new local Z axis.
One CSS variable geometry
--half derives from --size, so rescaling the cube keeps every face flush.
Preserve-3d parent
transform-style: preserve-3d keeps all six faces in real 3D space.
Perspective camera
A perspective value on the scene gives the rotation real depth.
Dual-axis tumble
rotateX and rotateY animate together for a genuine 3D roll.
Backface-hidden faces
Rotated-away faces don't show their mirrored backside through the cube.
Reduced-motion safe
Freezes at a static 3D angle instead of a flat front view when motion is off.

About this UI Snippet

3D Rotating Cube Loader — Six Faces, Correctly Positioned in 3D Space

Screenshot of the 3D Rotating Cube Loader snippet rendered live

A lot of "3D cube" loaders on the web are actually a single flat square with a fake shadow or a skewed pseudo-element — a 2D illusion, not a real cube. This snippet builds an actual six-sided CSS cube: six real elements, each individually rotated and pushed outward along the Z axis by exactly half the cube's edge length, so they form a genuinely closed 3D box that a perspective-enabled camera can rotate around and view from any angle, in plain HTML and CSS with no JavaScript at all.

Perspective, then a 3D-preserving parent

The outer .cb-scene sets perspective: 600px, which establishes the "camera distance" for everything inside it — without it, all the 3D transforms below would flatten back to 2D. The .cb-cube element inside is given transform-style: preserve-3d, which is what allows its six children to keep their individual 3D positions relative to each other and to the parent's own rotation, instead of each face's transform being flattened independently.

The correct six-face transform math

Every face is a full-size div absolutely positioned to overlap all the others at the cube's center, and each gets exactly two transforms in this order: a rotation, then a translation along Z. The rotation happens first (around the shared center), which reorients that face's own local Z axis; the translateZ(half) that follows then pushes the face outward along that newly-rotated axis, landing it flush against the correct side of the cube rather than at some skewed offset. Concretely: .cb-front needs no rotation, so translateZ(--half) alone pushes it straight toward the viewer. .cb-back is rotated 180° first so its own local Z axis now points away from the viewer, and the same translateZ(--half) push lands it on the opposite side. .cb-right and .cb-left rotate ±90° around Y before translating, and .cb-top/.cb-bottom rotate ±90° around X — six faces, six correctly-oriented pushes, one shared --half distance, forming a real closed cube with no gaps or overlaps.

One CSS variable drives the whole geometry

--size is defined once on .cb-scene, and --half is computed from it with calc(var(--size) / 2). Because every face's translation distance references --half rather than a hardcoded pixel value, changing --size alone rescales the entire cube correctly — every face stays flush with no gaps, since the geometry is expressed as a relationship rather than six independently-tuned numbers.

Spinning on two axes at once

The cube's own @keyframes animates transform: rotateX() rotateY() together from 0deg to 360deg on both axes simultaneously over one loop. Because both rotations run at the same rate, the cube traces a genuinely three-dimensional tumble rather than spinning flatly around a single vertical axis — every face becomes visible from a constantly-changing angle as the loop repeats.

backface-visibility keeps it clean

Each face has backface-visibility: hidden, so a face currently rotated away from the viewer doesn't render its (identical, mirrored) backside poking through — important once the whole cube is tumbling and faces are constantly swinging into and out of view.

Reduced motion

Under prefers-reduced-motion, the animation is disabled and the cube is frozen at a fixed, still genuinely three-dimensional angle (a static rotateX(-20deg) rotateY(35deg)) so users who opt out of motion still see the real 3D shape rather than a flattened front face. Pair it with an orbit loader or a wave loader for a non-3D alternative where perspective support or motion preference makes a real cube less appropriate.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA six-sided cube renders and spins continuously on two axes.
  2. 2
    Watch every face pass byAs it tumbles, each of the six numbered, differently-coloured faces comes into view.
  3. 3
    Inspect the face transformsNote each face is a rotation followed by translateZ(--half) — never a flat 2D trick.
  4. 4
    Resize the cubeChange --size on .cb-scene; --half recalculates and every face stays flush.
  5. 5
    Change the spin speed or axesEdit the cbSpin keyframe's duration or which axes it rotates.
  6. 6
    Recolor the facesEach .cb-face has its own gradient — restyle any or all of them.

Real-world uses

Common Use Cases

Distinctive app preloaders
Use a genuinely three-dimensional cube, with six real faces, as the loading mark for a dashboard or tool that wants a technical personality.
3D and gaming product sites
Match the visual language of a game launcher or WebGL showcase, where transform-style: preserve-3d keeps all six faces in real depth.
Portfolio and agency loaders
Create a memorable loading moment for a creative studio site that doubles as a small, visible demonstration of real CSS 3D skill.
Design system loader variants
Sit alongside a circular progress and a dots loader as the most dimensional option in a loader family.
CSS 3D transforms reference
Study the rotate-then-translate order for each face, and how --half derived from --size keeps the cube closed at any scale.
Related: Skeleton-to-Content Crossfade
See the Skeleton-to-Content Crossfade for a related loaders pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

Each of the six faces is its own element rotated around the cube's shared center and then translated outward along Z by exactly half the cube's edge length, positioned inside a parent with transform-style: preserve-3d and a perspective camera on its ancestor. This produces genuine spatial geometry a viewer can rotate around and see from any angle — a flat 2D trick would typically fake depth with a single skewed pseudo-element and a static shadow, which can't correctly reveal different faces as it turns.

The rotation runs first and reorients that face's own local Z axis to point toward the correct side of the cube; the translateZ that follows then pushes the face outward along that newly-rotated axis, landing it flush against the right side. If the order were reversed, or if translateZ were used without the matching rotation, the faces would translate along the original, un-rotated Z axis and stack incorrectly instead of forming a closed box.

--half is computed from --size with calc(var(--size) / 2), and every single face's translateZ distance references --half rather than a hardcoded value. Since the geometric relationship (each face sits exactly half the cube's own edge length from center) is expressed as a formula, not six independent numbers, changing --size once automatically keeps every face correctly flush against its neighbors at any size.

As the cube tumbles, a face that has rotated to point away from the viewer would otherwise still render — showing its own (identical, mirrored) reverse side bleeding through the cube from the wrong direction. backface-visibility: hidden prevents a face from rendering at all once it's turned away, keeping the cube looking solid and correctly opaque from every viewing angle during the spin.

Render the scene/cube/six-face markup as-is — the entire effect is CSS custom properties and keyframes with no JavaScript dependency, so no lifecycle hooks are required. If you want per-instance sizing, pass --size as an inline style or CSS variable prop on the .cb-scene element; the calc()-derived --half and all six face transforms will recalculate automatically.