Source Code

<div class="dr-stage">
  <div class="dr-scene">
    <div class="dr-cube" id="drCube">
      <div class="dr-face dr-f1"><span></span></div>
      <div class="dr-face dr-f2"><span></span><span></span></div>
      <div class="dr-face dr-f3"><span></span><span></span><span></span></div>
      <div class="dr-face dr-f4"><span></span><span></span><span></span><span></span></div>
      <div class="dr-face dr-f5"><span></span><span></span><span></span><span></span><span></span></div>
      <div class="dr-face dr-f6"><span></span><span></span><span></span><span></span><span></span><span></span></div>
    </div>
  </div>
  <button type="button" class="dr-btn" id="drBtn">Roll</button>
  <p class="dr-result" id="drResult">Tap roll</p>
</div>

Dice Roller — CSS 3D Dice Roll Animation (JS)

Dice Roller · Animations · Plain HTML, CSS & JS · Live preview

What's included

Features

Real CSS 3D cube
Six faces in a preserve-3d scene with perspective — a genuine 3D object, not a flat sprite.
Grid-area pip layouts
Each face's dots use named CSS grid areas for clean, declarative die faces.
Transform-based roll
Rolling is one CSS transition to a known orientation plus extra full turns to tumble.
Lands on the right face
The final rotation is the chosen face's base plus whole 360° turns, so it settles exactly.
Continuous forward spin
An accumulating spin count means the cube never snaps backwards between rolls.
Honest randomness
Math.random() picks the value before the animation, so the visual always matches the result.
Roll guard
The button disables during the tumble and re-enables with the result.
No images, no library
Pure HTML/CSS/JS — themeable and dependency-free.

About this UI Snippet

Dice Roller — A Real 3D CSS Cube That Tumbles to a Random Face

Screenshot of the Dice Roller snippet rendered live

A rolling 3D die is a small piece of delight that's also a perfect lesson in CSS 3D transforms. This snippet builds a real cube — six faces with correct pip layouts — that tumbles and lands showing a random face, in pure HTML, CSS, and a few lines of vanilla JavaScript, with no library and no images.

A genuine 3D cube, not a flat sprite

The cube is six div faces positioned in 3D space. The container has transform-style: preserve-3d so its children render in a shared 3D scene, and each face is pushed out from the centre with translateZ(48px) after being rotated to point in its direction — front, back, left, right, top, bottom. The scene has perspective so the cube looks dimensional rather than isometric. This is the canonical way to build a CSS cube, and it means the die is a real object you can rotate to any orientation.

Pips placed with CSS grid areas

Each face's dots are laid out with CSS grid template areas — a 3×3 grid where named cells hold the pips for that number. One pip sits in the centre cell; two sit in opposite corners; five fills the corners plus centre; and so on. Using named grid areas (rather than absolute positioning) makes each face's layout declarative and easy to read, and it's the cleanest way to get the classic die-face arrangements without hand-tuning pixel positions.

Rolling by rotating to a known orientation

The roll is one CSS transition. For each face there's a base rotateX/rotateY that brings it to the front, matching the face transforms. Rolling picks a random 1–6, looks up that base rotation, and adds several full 360° turns so the cube visibly tumbles before settling — landing exactly on the chosen face because the final orientation is a known value plus whole turns. Accumulating the spin count across rolls means the cube always rotates forward, never snapping backwards, so consecutive rolls look continuous.

Honest randomness and a settled result

The result is chosen with Math.random() *before* the animation, so the visual outcome always matches the reported number — the die isn't faked after the fact. The button disables during the ~1s tumble and re-enables with the result text when it lands, so users can't spam mid-roll and the announced value always corresponds to the resting face.

Drop-in and adaptable

The whole thing is self-contained and themeable with CSS. Use it for a board-game UI, a randomiser, a "pick for me" control, or just a tactile bit of fun. It's also a clear, complete reference for CSS 3D cubes, preserve-3d, and transform-based animation that you can adapt into any 3D interaction.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Rather than mentally rotating the cube yourself, paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why adding 360 times the spins counter to the FACE_ROT base rotation makes the cube land on the correct face every time, and how translateZ(48px) combined with preserve-3d positions all six faces without them overlapping. The same assistant can help optimize it, for example checking whether the spins counter should be reset or capped after many rolls to avoid an ever-growing transform value, and whether that has any real performance cost. It's also useful for extending the effect: ask it to support two dice rolled together with a combined total, add a physically-tumbling multi-axis animation instead of a single transform, or replace the click-to-roll with a shake/deviceorientation trigger on mobile. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a rolling 3D dice UI in plain HTML, CSS, and JavaScript using only CSS 3D transforms — no canvas, no images, no library.

Requirements:
- A cube built from six absolutely-positioned div faces inside a parent with transform-style: preserve-3d, each face pushed outward from the center with translateZ and rotated with rotateX/rotateY so it points in its own direction (front, back, left, right, top, bottom), inside an outer container with a CSS perspective value so the cube reads as genuinely three-dimensional.
- Each face's pips laid out with CSS grid-template-areas (a 3x3 named grid) rather than absolute pixel positions, matching the classic dot arrangements for 1 through 6.
- A lookup table mapping each face value 1-6 to the base rotateX/rotateY that brings that face to the front of the cube, matching the CSS transforms used to build the faces.
- A roll function that first picks a random result with Math.random() before any animation runs, then sets the cube's transform to that face's base rotation plus a growing whole-number multiple of 360 degrees (tracked in a counter that increases on every roll) so the cube visibly tumbles forward and always lands exactly on the chosen face, never snapping backward between rolls.
- Disable the roll trigger for the duration of the roughly one-second CSS transition and only report the rolled number in the UI once the transition's timeout fires, so the displayed result can never desync from the cube's resting orientation.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA 3D die renders on a dark stage with a Roll button.
  2. 2
    Roll itClick Roll (or the die) and the cube tumbles for about a second, landing on a random face.
  3. 3
    Read the resultThe text below shows the rolled value, which always matches the resting face.
  4. 4
    Roll againEach roll adds more turns so the cube keeps tumbling forward continuously.
  5. 5
    Restyle itChange the face gradient, pip colour, size, or perspective to fit your design.
  6. 6
    Hook into your gameRead the chosen number in roll() to drive a board game, randomiser, or picker.

Real-world uses

Common Use Cases

Board and tabletop game UIs
Drive turns with a tactile roll — pair with a spin wheel for other randomisers.
Randomisers and "pick for me"
Use the result to choose an option or order, alongside a confetti button for wins.
Quizzes and classroom tools
Pick a random student or question with a fun roll.
Gamified onboarding
Add playful chance to a flow next to a scratch card reveal.
Decision and party apps
A delightful way to leave a choice to chance.
Learning CSS 3D transforms
A complete reference for preserve-3d cubes — compare with a 3D card tilt.
Related: GSAP Draggable Inertia
See the GSAP Draggable Inertia for a related animations pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

Each face has a base rotateX/rotateY that brings it to the front, matching the CSS transforms that position the faces. Rolling picks a random 1–6, looks up that base rotation, and adds several full 360° turns. Because a full turn returns to the same orientation, the cube tumbles and then settles exactly on the chosen face — the final transform is "known landing orientation + whole turns."

Each face is a 3×3 CSS grid with named grid-template-areas, and the pips are placed into named cells (centre for 1, opposite corners for 2, all corners plus centre for 5, and so on). Declarative grid areas are cleaner and more readable than hand-tuned absolute coordinates, and they reproduce the classic die-face layouts exactly.

It's genuinely random and decided first. Math.random() picks the value before the animation starts, the cube is rotated to land on that face, and the same number is reported when it settles. The visual outcome and the announced result always match — nothing is changed after the tumble.

The roll is a ~1-second CSS transition. Disabling the button (and ignoring clicks on the cube) during that window prevents a new roll from interrupting the current animation, which would make the cube jump and could desync the displayed result from the resting face. It re-enables with the result text once the die settles.

In React, hold the current rotation/result in state and set the cube's transform style in the click handler, using a timeout to re-enable; in Vue, bind :style with a ref; in Angular, bind [style.transform] with a component property. The FACE_ROT lookup and rotation math are framework-agnostic — only the state and the transform binding move into the framework.