DEV Community

Cover image for Pure CSS 3D dice cube module: FSCSS
FSCSS tutorial for FSCSS tutorial

Posted on • Originally published at github.com

Pure CSS 3D dice cube module: FSCSS

cube.fscss — a module for FSCSS, Here's the whole thing rendered live, nothing but <div>s and CSS:

<script src="https://cdn.jsdelivr.net/npm/fscss@1.1.24/exec.min.js" defer></script>

<style>
@import((*) from cube)

@cube-root()
@cube-wrapper(.my-cube)
@cube-face(.my-cube div)
@cube-face-front(.my-cube .f1)
@cube-face-back(.my-cube .f2)
@cube-face-left(.my-cube .f3)
@cube-face-right(.my-cube .f4)
@cube-face-top(.my-cube .f5)
@cube-face-bottom(.my-cube .f6)
@cube-micro-dice(.my-cube .cube)

.my-cube {
  @cube-size(80px)
  transform: rotateY(50deg) rotateX(30deg);
  animation: spin 5s linear infinite;
}

@keyframes spin {
  0%   { transform: rotateX(20deg) rotateY(40deg); }
  100% { transform: rotateX(380deg) rotateY(400deg); }
}
</style>

<div class="my-cube">
  <div class="cube f1 dice-1"></div>
  <div class="cube f2 dice-2"></div>
  <div class="cube f3 dice-3"></div>
  <div class="cube f4 dice-4"></div>
  <div class="cube f5 dice-5"></div>
  <div class="cube f6 dice-6"></div>
</div>
Enter fullscreen mode Exit fullscreen mode

No dice PNGs. No SVG paths. No canvas draw calls. Every pip is a radial-gradient.

Why build this

Already had a chart/dashboard module (st-core.fscss) built on clip-path polygons — no JS charting library, just CSS custom properties driving the shape. I wanted to see how far that "CSS does the visual math" philosophy could stretch into actual 3D space. A cube felt like the right next test: real perspective, real preserve-3d, real face geometry — not a flat illusion.

How the geometry works

A cube is 6 absolutely-positioned faces sharing one parent set to transform-style: preserve-3d. Each face gets pushed out from the center by half the cube's edge length, then rotated to face outward:

front  → translateZ(half)
back   → rotateY(180deg) translateZ(half)
top    → rotateX(90deg)  translateZ(half)
bottom → rotateX(-90deg) translateZ(half)
right  → rotateY(90deg)  translateZ(half)
left   → rotateY(-90deg) translateZ(half)
Enter fullscreen mode Exit fullscreen mode

The entire cube's dimensions live in two CSS variables: --cube-size and --cube-half (always size ÷ 2). Resize the whole thing with one call:

.my-cube {
  @cube-size(150px)
}
Enter fullscreen mode Exit fullscreen mode

Faces, offsets, and dice dot size all scale together — no manually recalculating six translateZ values by hand.

Dice pips, no font glyphs

I originally reached for Unicode dice characters (⚀⚁⚂⚃⚄⚅) for the pips, but font rendering for those glyphs is wildly inconsistent across platforms — some Android/Chrome stacks show them thin, outlined, or as tofu boxes. Stacked radial-gradients solved it instead:

.dice-3 {
  background-image:
    radial-gradient(circle var(--cube-dot-size) at 25% 25%, var(--cube-dot-color) 99%, transparent 100%),
    radial-gradient(circle var(--cube-dot-size) at 50% 50%, var(--cube-dot-color) 99%, transparent 100%),
    radial-gradient(circle var(--cube-dot-size) at 75% 75%, var(--cube-dot-color) 99%, transparent 100%);
}
Enter fullscreen mode Exit fullscreen mode

Font-independent, crisp at any size, and the dot radius is itself a variable — so it scales automatically with @cube-size() instead of overflowing on a big cube or vanishing on a small one.

It's not just dice

Faces and face content are decoupled on purpose. The face mixins only handle position — what renders inside is a separate "micro" you apply on top. Right now there are eight:

  • cube-micro-fire — pulsing gradient-text fire
  • cube-micro-matrix — scrolling digital rain
  • cube-micro-neon — pulsing cyan/magenta glow border
  • cube-micro-float — subtle bounce, stacks with anything
  • cube-micro-glass — frosted backdrop-filter panel
  • cube-micro-holo — holographic shifting gradient text
  • cube-micro-glitch — RGB-split glitch shift
  • cube-micro-dice — the pips above

Mix and match per face on the same cube:

@cube-micro-fire(.my-cube .f1)
@cube-micro-matrix(.my-cube .f2)
@cube-micro-neon(.my-cube .f3)
@cube-micro-glass(.my-cube .f5)
@cube-micro-holo(.my-cube .f6)
Enter fullscreen mode Exit fullscreen mode

Try it

CDN, drop-in:

<script src="https://cdn.jsdelivr.net/npm/fscss@1.1.24/exec.min.js" defer></script>
<style> 
  @import((*) from cube)
  /* your mixins here */
</style>
Enter fullscreen mode Exit fullscreen mode

Or compile ahead of time with the CLI for a plain .css file, zero runtime:

npm install -g fscss
fscss style.fscss style.css
Enter fullscreen mode Exit fullscreen mode

Full docs, token reference, and more examples: github.com/fscss-ttr/cube.fscss

If you build something with it — I'd love to see. And if you hit issue, open an issue, it's open-source.


Stay tuned for more upcoming useful modules ❤️

Top comments (0)