Dice button

A dice button animated with CSS 3D Transforms.

The button is a CSS 3D cube - six <div> faces assembled in 3D space. Clicking it rotates the whole cube so a different face points toward the viewer. No canvas, no SVG swap - just transform-style: preserve-3d and a spring animation.

Building the cube

Each face is an absolutely-positioned <div> that is first rotated to face its direction, then pushed outward by translateZ(12.5px) - half the cube's 25px side length. The parent has transform-style: preserve-3d so the browser renders all six children in actual 3D space rather than flattening them:

face 1  rotateY(0deg)    translateZ(12.5px)  // front
face 2  rotateY(90deg)   translateZ(12.5px)  // right
face 3  rotateX(90deg)   translateZ(12.5px)  // top
face 4  rotateX(-90deg)  translateZ(12.5px)  // bottom
face 5  rotateY(-90deg)  translateZ(12.5px)  // left
face 6  rotateY(180deg)  translateZ(12.5px)  // back

Showing a face

Rather than moving the faces, the parent cube is rotated so the target face points at the camera. Each face number maps to a { rotateX, rotateY } pair - for example, bringing face 2 (right side) forward requires rotating the cube -90deg on the Y axis. These values are passed to Motion's animate prop, which spring-animates between rotations on every click.

Drawing the pips

Each face renders a <DiceFaceIcon> that places small circles onto an invisible 3×3 grid. The grid uses columns and rows indexed 0–2, and each pip is positioned with left: col × 37.5% and top: row × 37.5%. A lookup table holds the grid coordinates for every face value - face 5, for instance, fills the four corners plus the center.

Click and spin

On every click, pickRandomFace selects a new face that is never the same as the current one, updates state, and the spring animation kicks in. A spinning prop is also exposed: when true, a setInterval cycles faces every 125 ms - useful for signalling that an async action is in progress, as shown in the demo above.

If the user has enabled the OS-level reduced-motion preference, useReducedMotion disables all spring transitions (duration 0) and removes the tap-scale effect, so the button stays fully accessible.

Newsletter

Stay updated with my latest articles and projects. No spam, no nonsense.