Isometric Cube
An isometric 3D cube in pure CSS transforms — three faces tinted from one base color (lighter top, base front, darker right), optional logo on top, optional slow spin. Extracted from explorer.b3.fun.
Usage
import { Cube } from "~/components/3d/cube";<Cube color="#3b82f6" size={72} spin /><Cube color="#8b5cf6" imageUrl="/logo.svg" />
Three faces, one color
A cube only needs three faces if you're looking at one corner. Each is a square rotated to its plane and pushed out by half the cube's size with translateZ, all inside a preserve-3d parent tilted to an isometric angle. No library, no canvas, just transforms.
The nice touch is the shading. Instead of picking three colors, it takes one hex and derives the faces by nudging lightness in HSL: the top is brightest, the front sits in the middle, and the right face stays the base color as the shadowed side. So you pass a single brand color and the cube looks lit. Add spin and a keyframe rotates the parent a full turn on Y.
// three faces, each rotated out and pushed half the cube forwardconst half = size / 2;<div style={{ transformStyle: "preserve-3d",transform: "rotateX(29.264deg) rotateY(-225deg)" }}><div style={{ background: front, transform: `rotateY(0deg) translateZ(${half}px)` }} /><div style={{ background: right, transform: `rotateY(90deg) translateZ(${half}px)` }} /><div style={{ background: top, transform: `rotateX(90deg) translateZ(${half}px)` }} /></div>// face colors come from ONE hex via HSL lightness nudgesconst front = hexToHSL(color, 0.22); // liftedconst right = color; // base (the dark side)const top = hexToHSL(color, 0.30); // brightest
Props
| prop | type | default | description |
|---|---|---|---|
| color | string | — | Base hex; faces derive from it. |
| imageUrl | string | — | Optional logo on the top face. |
| size | number | 72 | Cube size in px. |
| spin | boolean | false | Slowly rotate on the Y axis. |
From explorer.b3.fun, a project I work on at B3.
Source
download .zipThe full implementation, across 2 files. Copy it in or grab the zip. It leans on a cn() class helper and the theme tokens (--primary, --border, …).
import type { CSSProperties } from "react";import { cn } from "~/lib/utils";/** Convert a hex color to an HSL string, nudging lightness by `lAdjust` (−1..1). */function hexToHSL(hex: string, lAdjust = 0): string {hex = hex.replace("#", "");let r = 0,g = 0,b = 0;if (hex.length === 3) {r = parseInt(hex[0] + hex[0], 16);g = parseInt(hex[1] + hex[1], 16);b = parseInt(hex[2] + hex[2], 16);} else if (hex.length === 6) {r = parseInt(hex.slice(0, 2), 16);g = parseInt(hex.slice(2, 4), 16);b = parseInt(hex.slice(4, 6), 16);}r /= 255;g /= 255;b /= 255;const max = Math.max(r, g, b),min = Math.min(r, g, b);let h = 0,s = 0;let l = (max + min) / 2;if (max !== min) {const d = max - min;s = l > 0.5 ? d / (2 - max - min) : d / (max + min);switch (max) {case r:h = (g - b) / d + (g < b ? 6 : 0);break;case g:h = (b - r) / d + 2;break;case b:h = (r - g) / d + 4;break;}h /= 6;}l = Math.min(1, Math.max(0, l + lAdjust));return `hsl(${Math.round(h * 360)}, ${Math.round(s * 100)}%, ${Math.round(l * 100)}%)`;}export interface CubeProps {/** Base hex color; faces are derived from it. */color: string;/** Optional logo/image on the top face. */imageUrl?: string;size?: number;/** Slowly rotate on the Y axis. */spin?: boolean;className?: string;}/*** An isometric 3D cube in pure CSS transforms — three visible faces, each tinted* from one base color (lighter top, base front, darker right), with an optional* logo on top. Extracted from explorer.b3.fun.*/export function Cube({ color, imageUrl, size = 72, spin, className }: CubeProps) {const front = hexToHSL(color, 0.22);const right = color;const top = hexToHSL(color, 0.3);const bottom = hexToHSL(color, -0.1);const half = size / 2;const face = (bg: string, transform: string): CSSProperties => ({position: "absolute",width: size,height: size,background: bg,transform,});return (<div className={cn("relative flex items-center justify-center", className)}><divclassName={cn("cube-3d", spin && "cube-3d-spin")}style={{width: size,height: size,position: "relative",transformStyle: "preserve-3d",transform: "rotateX(29.264deg) rotateY(-225deg)",}}>{/* hidden until spun: back / left / bottom complete the cube */}<div style={face(front, `rotateY(180deg) translateZ(${half}px)`)} /><div style={face(right, `rotateY(-90deg) translateZ(${half}px)`)} /><div style={face(bottom, `rotateX(-90deg) translateZ(${half}px)`)} /><div style={face(front, `rotateY(0deg) translateZ(${half}px)`)} /><div style={face(right, `rotateY(90deg) translateZ(${half}px)`)} /><divstyle={{...face(top, `rotateX(90deg) translateZ(${half}px)`),display: "flex",alignItems: "center",justifyContent: "center",}}>{imageUrl && (<divstyle={{width: size * 0.6,height: size * 0.6,transform: "rotate(180deg)",backgroundImage: `url("${imageUrl}")`,backgroundSize: "contain",backgroundRepeat: "no-repeat",backgroundPosition: "center",}}/>)}</div></div></div>);}
/* ── Isometric CSS cube spin, from explorer.b3.fun ───────────────────────── */.cube-3d-spin {animation: cube-spin 9s linear infinite;}@keyframes cube-spin {from { transform: rotateX(29.264deg) rotateY(-225deg); }to { transform: rotateX(29.264deg) rotateY(135deg); }}@media (prefers-reduced-motion: reduce) {.cube-3d-spin { animation: none; }}