Skip to content
WANCRAFT

H.2113D & Experimental

Dice Roller

CSS 3D dice with real pips that hop and tumble through several turns, then settle on a random face and announce the total.

NewReactMotionCSSTailwindintermediate

Overview

Dice Roller builds each die as a CSS 3D cube: six faces with properly laid-out pips (opposite faces add up to seven) and inner planes that fill the rounded corners, so it reads as solid from any angle. A roll picks its faces in the event handler, with Math.random or a seeded generator for repeatable tests. Every die then spins through several extra turns on two axes while it hops and its shadow shrinks and grows, and it lands exactly on the chosen face. The dice start a beat apart so a handful doesn't move in lockstep. Roll with the button, a click on the tray, or R while the component has focus. Once the dice settle, a polite live region announces "Rolled 4 and 6, total 10" and the total updates. The first render always shows the same faces, so server and client markup match. With reduced motion the faces change instantly.

01Installation

Method 01 · Registry

CLI verified

Uses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/dice-roller.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/dice-roller.json

Method 02 · Copy and paste

  1. 1. Install the dependencies below.
  2. 2. Copy the source from the Code tab into components/ui/dice-roller.tsx.
  3. 3. Import it and adjust props.
npm install motion
Dependencies
motion
Peer
react ≥ 18, tailwindcss ≥ 4
Added
13 Oct 2026
Updated
—

02Usage

The default configuration. Change properties above to generate a tailored example in the Usage tab.

example.tsx
import { DiceRoller } from "@/components/ui/dice-roller";

export function DiceRollerExample() {
  return (
    <DiceRoller onRoll={(values) => console.log(values)} />
  );
}

03Props

PropTypeDefaultDescription
countnumber2Number of dice, 1–5.
sizenumber72Edge length of each die in px.
durationnumber1400Roll length in ms.
spinsnumber2Extra full turns before settling.
initialValuesnumber[][6, 3, 5, 2, 4]Faces shown before the first roll (deterministic for SSR).
seednumber—Seed a repeatable sequence of rolls (mulberry32). Omit for Math.random.
onRoll(values: number[]) => void—Called with the new faces when a roll starts.
showTotalbooleantrueShow the faces and their sum beside the button.
dieColor / pipColorstring"#F7F5F0" / "#17191C"Die body and pip colours.
accentstring"#FF7448"The single pip on the 1 face, and focus rings.
labelstring"Dice"Accessible name of the dice group.

04Features

  • 01Solid CSS 3D cubes with correctly laid-out pips and filled rounded corners
  • 02Random faces chosen in the event handler; deterministic first render; optional seed
  • 03Several extra turns on two axes with a hop, a breathing shadow and a staggered start per die
  • 04Lands exactly on the chosen face every time (angles are continued, never reset)
  • 05Roll by button, tray click or R while focused; onRoll callback
motion · tweenmotion · staggerinput · clickinput · keyboard

05Accessibility

  • A labelled group of dice, each role="img" with a label such as "Die 1 showing 4" ("rolling" while in motion).
  • A real Roll button (aria-keyshortcuts="R"); R rolls whenever focus is inside the component, never globally, and it ignores modified keys.
  • A polite live region announces "Rolled 4 and 6, total 10" once the dice settle.
  • The visual total is aria-hidden to avoid announcing it twice.
  • prefers-reduced-motion changes the faces instantly, with no tumble or hop.