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) // backShowing 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.