Loaders✨ Premium

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.

CSSGrid

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

Photo gallery — loading an album of thumbnails — Cube Grid example 1

① Photo gallery — loading an album of thumbnails

WhenThe user opens an album; the thumbnails have not arrived yet. The grid sits at the centre of the area where the first 9 thumbnails will appear, then clears as they load.
WhyThe 3 × 3 checkerboard is a miniature of the image grid it stands in for: the loader takes the shape of the content to come, which a circular spinner or a bar cannot do. The diagonal wave also follows the order in which the thumbnails appear, top-left to bottom-right.
SettingsScale up to 84 × 84 px (width/height: 84px on the grid, 24px on each cube, gap: 6px) so the wave stays legible above 160 px thumbnails; default palette; role="status" aria-label="Loading album" on the grid, aria-busy="true" on the thumbnail area.
Dashboard cell — metric being computed — Cube Grid example 2

② Dashboard cell — metric being computed

WhenAn analytics dashboard displays cells whose values arrive from the API with a delay. The grid holds the cell until the response lands, then is replaced by the figure.
WhyThe square grid occupies a fixed, predictable footprint — it does not shift the rest of the table. Its checkerboard pattern fits the tabular context visually and avoids being mistaken for an error state.
SettingsCentre with display: flex; align-items: center; justify-content: center on the cell; neutral colours #94a3b8 (rest) and #cbd5e1 (minimum) to avoid competing with the data.
Booking calendar — searching for available slots — Cube Grid example 3

③ Booking calendar — searching for available slots

WhenThe user has picked dates and a headcount; the system checks the availability of every slot. The grid covers the slot table (3 days × 3 time ranges) until the answer lands.
WhyThe 9 cubes map one to one onto the 9 slots being queried: the loader is a slot table in miniature. Each cube that shrinks away and comes back reads as a cell being checked, and the diagonal wave sweeps the table the way the query walks through the days and then the hours — no circular loader tells that story.
Settings72 × 72 px (width/height: 72px on the grid, 20px on each cube, gap: 6px); keep the 1.3 s cycle; role="status" aria-label="Checking availability" on the grid, aria-busy="true" on the slot table.

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 9 cubeGridScale animations 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, or aria-busy="true" on the element it covers. If a parent already carries that role, set aria-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 #6366f1 reaches 4.0:1 and fuchsia #d946ef 4.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.

Chrome 57+✓ Full
Firefox 52+✓ Full
Safari 10.1+✓ Full
Edge 79+✓ Full
iOS Safari 10.3+✓ Full
Android Chrome✓ Full

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):

index.html — structure
<div class="loader-cube-grid">
  <span></span>
  <span></span>
  <!-- … 9 spans in total -->
</div>
🔒 Unlock the full code — from €2.99 the first month

Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
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

Cube Flip is a single 40 × 40 px cube that performs two consecutive 3D half-turns (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.
Yes, by reversing the delays. The inverted formula is (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.
No. The 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.