Cube Flip
A 40 px square with an indigo → purple gradient, two chained 180° flips (X axis then Y axis) over 1.2 s, inline 120 px perspective — one div, zero JS.
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 effect is a single element: .loader-cube, a 40 × 40 px square, linear-gradient(135deg, #6366f1, #8b5cf6) background (indigo → purple). No children, no JavaScript, no CSS variables.
The cubeFlip animation (1.2 s, ease-in-out, infinite) splits the cycle into two equal 0.6 s phases. Phase 1 (0% → 50%): rotateX goes from 0° to −180° — the square tips forward, its top edge coming toward the viewer. Phase 2 (50% → 100%): rotateX stays at −180°, rotateY goes from 0° to −180° — the already-flipped element pivots on its vertical axis. The value perspective(120px) is written into the transform property of each keyframe rather than on a parent: every instance has its own independent vanishing point. On a 40 px element, the perspective-to-size ratio is 3:1 — noticeable but not exaggerated.
Since no backface-visibility: hidden or transform-style: preserve-3d is declared, the element's back face is visible at 180° — it is the same gradient, flipped horizontally (the indigo corner moves from top-left to top-right). Measured in Chromium: near the axis (~77° of rotation around X), the rendered height of the 40 px element drops to 9 px and the width rises to 48 px from the near-edge enlargement effect of the perspective.
Accessibility
- prefers-reduced-motion not handled: the
cubeFlipanimation runs continuously regardless of system preferences — measured:animationPlayState: runningunderprefers-reduced-motion: reduce. Add:@media (prefers-reduced-motion: reduce) { .loader-cube { animation: none; } }. - The cube is purely visual: no
role, no text, noaria-liveregion. As a loading indicator it must be paired with a visually hidden element carryingrole="status"oraria-live="polite"(e.g.<span class="sr-only" role="status">Loading…</span>). Setaria-hidden="true"on.loader-cubeso screen readers do not traverse the decorative element. - Contrast: the indigo gradient (#6366f1) on white yields only 2.6:1, below the 3:1 threshold for non-text UI components; on the code's dark background (
#0a0a0f) it reaches 4.7:1. On a light page, darken the hues (e.g.#4338ca → #7c3aed). - The 40 × 40 px element is not interactive. If placed inside a button, the
<button>carries the focus and the ARIA label — the cube receivesaria-hidden="true".
Browser compatibility
Requires @keyframes, animation, transform: perspective() rotateX() rotateY() and linear-gradient. No library, no CSS variables, no JavaScript.
Without rotateX/rotateY, the square remains static in indigo. Without @keyframes, same result — a visible static square.
The code
HTML structure to paste into your page (CSS + JS available with a premium account):
<div class="loader-cube"></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 |
|---|---|---|
Size (CSS — .loader-cube) |
40 × 40 px | Height and width in px or em. The perspective is in absolute pixels (120px): if you scale the element up significantly, increase the perspective proportionally to keep the same foreshortening ratio. |
Background color (CSS — background) |
linear-gradient(135deg, #6366f1, #8b5cf6) | Gradient or solid color. The back face shows the same gradient flipped horizontally; a solid color removes this mirror effect. |
Duration (CSS — animation: 1.2s …) |
1.2 s | Time for one full cycle (two flips). 0.8s = hurried pace; 2s = slow, deliberate rotation. |
Easing (CSS — ease-in-out) |
ease-in-out | Acceleration curve applied to both phases independently. linear gives constant rotation speed; ease-in-out produces an acceleration at the center of each half-turn and a brief rest at each full-face position. |
Perspective (CSS — perspective(120px) in each keyframe) |
120 px (3:1 ratio relative to the 40 px element) | Simulated viewing distance. 80px (2:1) = strong 3D effect; 200px (5:1) = near-isometric rotation, little foreshortening. Change the value in all three keyframes (0%, 50%, 100%) at once. |
Back face visibility (add — backface-visibility: hidden) |
back face visible (CSS default) | Without this rule, the reversed gradient is visible at 180°. With backface-visibility: hidden, the element disappears at the axis crossing, creating a clean blank phase between the two face orientations. |
| Reduced motion (add) | not in the sold code | @media (prefers-reduced-motion: reduce) { .loader-cube { animation: none; } } freezes the cube. Alternatively, increase the duration to 4s for very slow motion rather than stopping it entirely. |
FAQ
transform-style: preserve-3d and no child faces. The 3D illusion comes from perspective(120px) rotateX() rotateY() applied to a flat element: as it tips, the perspective distorts its projection, compressing it down to 9 px tall at the axis crossing. The back face is the same gradient reversed — not a distinct face.scale 1 → 0 → 1) with staggered delays — an agitated grid animation. Cube Flip is a single element performing a real 3D rotation in two distinct phases on two axes. The techniques have nothing in common: Cube Grid uses no perspective or rotateX/rotateY, and Cube Flip has no grid or timing offset. Note: the CSS sold with Cube Flip mistakenly includes the full Cube Grid code (@keyframes cubeGridScale and the 11 .loader-cube-grid rules) — it can be deleted without affecting Cube Flip.perspective property on a parent creates a shared 3D space for all children — one vanishing point, centered on the parent. The perspective() transform function creates a 3D space local to each element, centered on itself. Consequence here: two .loader-cube instances side by side each have their own vanishing point and animate independently, without one element's perspective distorting the other.