Cube Grid
Nine 14 px cubes in a CSS 3 × 3 grid; scaled to zero at 35% of the 1.3 s cycle; delay (row + col) × 0.1 s — diagonal wave from 0 to 0.4 s. No JavaScript.
You'll get access to the interactive demo with a free account.
This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Loaders has 29 effects, including 4 free. Explore the category →
3 usage examples



How it works
The sold HTML holds a .loader-cube-grid with 9 <span> elements. The grid measures 54 × 54 px: display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px. Each cube is 14 × 14 px, border-radius: 2px, background #6366f1. Measured in Chromium: the computed column width is 15.33 px — (54 − 2 × 4) / 3 — leaving 0.67 px of spare space on each side of the cube, which has no visible effect. No JavaScript: the effect is entirely CSS.
The cubeGridScale animation (1.3 s, ease-in-out, infinite) runs in three measured phases. From 0 to 35% (0.455 s), each cube goes from scale(1) to scale(0) and from #6366f1 (indigo) to #d946ef (fuchsia) — it disappears as its colour changes. From 35 to 70% (0.455 s), it returns to scale(1) and #6366f1. From 70 to 100% (0.39 s), it holds at full size, indigo — a 30% rest phase before the next cycle starts.
Each cube's delay follows the formula (row + column) × 0.1 s (zero-based indices): cube (0, 0) starts at 0 s, cubes (0, 1) and (1, 0) at 0.1 s, cubes (0, 2), (1, 1) and (2, 0) at 0.2 s, cubes (1, 2) and (2, 1) at 0.3 s, cube (2, 2) at 0.4 s. The three anti-diagonals fire in succession — the wave crosses the grid from the top-left corner to the bottom-right corner in 0.4 s, less than a third of the cycle.
Accessibility
- prefers-reduced-motion not handled: no
@media (prefers-reduced-motion: reduce)rule in the code — measured under Playwright emulation, all 9cubeGridScaleanimations keep running,animationPlayState: running. Add@media (prefers-reduced-motion: reduce) { .loader-cube-grid span { animation: none } }to freeze the cubes at full size. - A loading indicator must be announced to assistive technologies. The code provides no semantics: add
role="status" aria-label="Loading"to.loader-cube-grid, oraria-busy="true"on the element it covers. If a parent already carries that role, setaria-hidden="true"on the grid instead. - The 9
<span>elements are empty and silent. No text, no interaction, no keyboard events: the effect is entirely passive — no focus conflict, no keyboard trap. - Contrast is not applicable: the cubes are passive decorative elements (the 3:1 threshold does not apply to non-interactive components carrying no information). Measured on
#0a0a0f: indigo#6366f1reaches 4.0:1 and fuchsia#d946ef4.8:1 — both pass the 3:1 threshold.
Browser compatibility
Requires display: grid, grid-template-columns, gap on a grid, and @keyframes with transform: scale() and a background change. No site design-system CSS variables, no dependency.
Without display: grid (IE 11 has partial prefixed support), the 9 spans display inline. Without @keyframes, 9 static indigo squares — not a broken render, just no animation.
The code
HTML structure to paste into your page (CSS + JS available with a premium account):
<div class="loader-cube-grid">
<span></span>
<span></span>
<!-- … 9 spans in total -->
</div>
Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Cube size (CSS — width/height on .loader-cube-grid span) |
14px | The grid does not resize automatically: also adjust width/height on .loader-cube-grid (3 × size + 2 × gap). At 8 px (gap 2 px) the grid is 28 px — fits inside a compact button. |
Gap between cubes (CSS — gap on .loader-cube-grid) |
4px | 2px: tightly packed, the cluster looks almost solid; 6px: airy grid, each cube stands apart. At 0, the cubes are flush. |
Rest colour (CSS — background: #6366f1 on .loader-cube-grid span and keyframe 0%/70%/100%) |
#6366f1 (indigo) | Colour visible for 70% of the cycle. Both occurrences (base rule and keyframe) must match — the base rule shows before the first cycle completes. |
Minimum colour (CSS — keyframe 35%, background: #d946ef) |
#d946ef (fuchsia) | Colour visible during the transition to scale(0) and back. It stays perceptible even when the cube is very small near zero. |
Cycle duration (CSS — 1.3s on .loader-cube-grid span) |
1.3 s | 0.8 s: fast wave; 2 s: slow, almost decorative. Below 0.5 s, the maximum delay of 0.4 s covers 80% of the cycle and the diagonal pattern breaks down — also shorten the delays. |
Wave rhythm (CSS — delays of the nine nth-child rules) |
0.1 s per diagonal step (0 s to 0.4 s) | 0.05 s per step: near-simultaneous cubes, very fast wave (max delay 0.2 s); 0.2 s: clearly staggered (max delay 0.8 s). The nine values must be recalculated by hand: (row + col) × step. |
Column count and cube count (CSS — repeat(3, 1fr); HTML — number of <span>) |
3 columns, 9 cubes | A 4 × 4 grid (16 spans, delays 0 to 0.6 s) or 2 × 2 (4 spans, delays 0 to 0.2 s) works with the same pattern — update grid-template-columns, the grid size and the nth-child rules. |
FAQ
rotateX −180° then rotateY −180°) in 1.2 s — one element, one perspective, one rotation. Cube Grid is nine cubes scaled in 2D in a diagonal wave: no rotation, no perspective, nine elements. Both use #6366f1 but share no code in their own files.(4 − row − col) × 0.1 s (zero-based): nth-child(1) → 0.4 s, nth-child(2) → 0.3 s, nth-child(3) → 0.2 s, nth-child(4) → 0.3 s, nth-child(5) → 0.2 s, nth-child(6) → 0.1 s, nth-child(7) → 0.2 s, nth-child(8) → 0.1 s, nth-child(9) → 0 s.js key in the sold file is empty. Everything relies on @keyframes and nine nth-child rules. No initialisation, no event listener, no dependency — dropping the HTML and CSS into a page is all that is needed.