9 CSS 3D Cube Effects — One Line, Copy-Paste
A css 3d cube spins its four labeled faces inside a perspective wrapper — one of nine such effects below, each one line, copy-paste ready in SCSS, with a
Auto-plays · click a tile to jump to its section · all nine in one zip
- 01 Cube rotate
- 02 3D carousel
- 03 Tilt on hover
- 04 Book open effect
- 05 Flip gallery
- 06 Coverflow
- 07 Rotating stage
- 08 Perspective text
- 09 3D room scroll
The nine below build up the three properties that give CSS a sense of depth in the first place — perspective for how close the camera sits, transform-style: preserve-3d so a rotated child keeps its depth instead of getting flattened by its parent, and backface-visibility for hiding whichever face is turned away. 01 through 03 show each of those three doing the least amount of work possible. 04 through 06 stack several faces or several cards inside one shared 3D parent. 07 through 09 build a whole stage rather than a single spinning object. Each code block below keeps only the rule that matters for that item; the full file for every one sits in the zip.
01Cube rotate
A four-sided cube spins on its Y axis inside a perspective container, cycling a different label onto each face as it turns. It suits a hero banner or a product tile that needs to rotate through three or four short messages without any script running the show.
.cube {
transform-style: preserve-3d;
animation: spin $duration $easing infinite;
}
.cube__face {
position: absolute; inset: 0;
backface-visibility: hidden;
}
.cube__face--front { transform: translateZ($half); }
.cube__face--right { transform: rotateY(90deg) translateZ($half); }
.cube__face--back { transform: rotateY(180deg) translateZ($half); }
.cube__face--left { transform: rotateY(-90deg) translateZ($half); }
@keyframes spin {
from { transform: rotateY(0deg); }
to { transform: rotateY(360deg); }
}
023D carousel
Five cards stand around a circle at fixed rotateY offsets, and spinning only the parent ring carries all five through a full turn together. Post 028's marquee pushes its row sideways with translateX; this ring never moves along that axis at all — rotation alone does the work. It fits a logo showcase or a rotating product badge that needs to keep circling on its own.
.ring {
transform-style: preserve-3d;
animation: orbit $duration $easing infinite;
}
// Five cards spaced 72deg apart around the circle — Sass multiplies the
// angle first so the browser never sees rotateY(0 * 72deg)
@for $i from 1 through 5 {
.ring__card:nth-child(#{$i}) {
transform: rotateY(#{($i - 1) * $step}) translateZ($radius);
}
}
@keyframes orbit {
from { transform: rotateY(0deg); }
to { transform: rotateY(360deg); }
}
03Tilt on hover
A card tilts to one fixed angle the moment a cursor, a keyboard focus ring, or a tap lands on it. Post 032's parallax tilt reads live pointer coordinates through JavaScript on every frame; this one transitions a single :hover value with zero script involved. A touch screen without any hover state still reaches the same tilted angle through an @media (hover: none) fallback on :active.
.frame { perspective: 600px; }
.tilt {
transform: rotateX(0deg) rotateY(0deg);
transition: transform $duration $easing;
}
.tilt:hover,
.tilt:focus-visible,
.tilt:active {
transform: rotateX(10deg) rotateY(-14deg);
}
@media (hover: none) {
.tilt:active { transform: rotateX(10deg) rotateY(-14deg); }
}
04Book open effect
A cover swings open around its left edge to rotateY(-150deg) and reveals the page sitting underneath before easing shut again. Post 006's flip card spins an entire card 180deg around its own center; here only one edge acts as a hinge and the cover never turns all the way over. It works for an onboarding guide or a catalog whose front matter wants to look like paper rather than a flat tile.
.book__cover {
transform-style: preserve-3d;
transform-origin: left center;
animation: open $duration $easing infinite;
}
.book__face {
position: absolute; inset: 0;
backface-visibility: hidden;
}
.book__face--front { background: $color; }
.book__face--back { background: darken($color, 14%); transform: rotateY(180deg); }
@keyframes open {
0%, 15% { transform: rotateY(0deg); }
55% { transform: rotateY(-150deg); }
85%, 100% { transform: rotateY(0deg); }
}
05Flip gallery
Four grid tiles flip one after another, each one starting 0.15s after the last, so the reveal ripples across the set instead of firing all at once. Where post 006 flips a single card end to end, this spreads that same rotateY flip over four tiles on a staggered delay. It suits a media gallery grid or a dashboard set introducing several categories at once.
.tile__inner {
transform-style: preserve-3d;
}
.tile__face {
position: absolute; inset: 0;
backface-visibility: hidden;
}
.tile__face--back { transform: rotateY(180deg); }
.tile:nth-child(1) .tile__inner { animation: flip $duration $easing infinite; }
.tile:nth-child(2) .tile__inner { animation: flip $duration $easing infinite; animation-delay: .15s; }
@keyframes flip {
0%, 15% { transform: rotateY(0deg); }
50% { transform: rotateY(180deg); }
85%, 100% { transform: rotateY(360deg); }
}
06Coverflow
Five cards share one keyframes animation, offset from one another only by a negative animation-delay, so the center card sits large and facing forward while the two on each side shrink and turn away. Nothing calculates which card counts as centered — the delay math alone keeps all five cycling through their slots. It suits an album or a portfolio-cover slider that needs to keep flowing without a script deciding the order.
.flow__card {
animation: flow $duration $easing infinite;
animation-delay: calc(var(--i) * -0.4s);
}
@keyframes flow {
0% { transform: translateX(-130px) translateZ(-90px) rotateY(45deg) scale(.6); }
40% { transform: translateX(0) translateZ(0) rotateY(0deg) scale(1); }
80% { transform: translateX(130px) translateZ(-90px) rotateY(-45deg) scale(.6); }
100% { transform: translateX(-130px) translateZ(-90px) rotateY(45deg) scale(.6); }
}
07Rotating stage
An object turns once every five seconds on a tilted, ellipse-shaped floor that never moves on its own. That's noticeably slower than the cube in item 01, and the added floor is what makes it read as a turntable rather than a bare spinning box. It fits a 360-degree new-product preview on an e-commerce detail page.
.turntable__floor {
border-radius: 50%;
transform: translateX(-50%) rotateX(70deg);
}
.turntable__object {
transform-style: preserve-3d;
animation: turn $duration $easing infinite;
}
.turntable__face {
position: absolute; inset: 0;
backface-visibility: hidden;
}
.turntable__face--back { transform: rotateY(180deg); }
@keyframes turn {
from { transform: rotateY(0deg); }
to { transform: rotateY(360deg); }
}
08Perspective text
Eight layers of text-shadow, each one shade darker than the one before it, give a heading the look of extruded thickness. The whole sign then rotates on rotateX around its bottom edge and eases back, like a sign swinging on a hinge fixed at its base. It fits a landing hero title or an event banner headline that wants a little dimension without loading a 3D library for it.
.sign {
text-shadow: $extruded-shadow;
transform-origin: center bottom;
animation: tilt $duration $easing infinite;
}
@keyframes tilt {
0%, 20% { transform: rotateX(0deg); }
55% { transform: rotateX(-34deg); }
100% { transform: rotateX(0deg); }
}
093D room scroll
A room missing its front wall — three walls plus a floor — turns once every nine seconds under a short, 260px perspective, so the view reads like a camera standing inside looking around rather than an object being examined from outside it. Unlike the closed six-sided cube in item 01, this structure is open and viewed from within. Despite the name, nothing here listens for an actual scroll event — the turn runs on the same kind of CSS loop as the rest of this set.
.room-scene { perspective: 260px; }
.room {
transform-style: preserve-3d;
animation: look-around $duration $easing infinite;
}
.room__wall {
position: absolute; inset: 0;
backface-visibility: hidden;
}
.room__wall--back { transform: translateZ(-$half); }
.room__wall--left { transform: rotateY(90deg) translateZ(-$half); }
.room__wall--right { transform: rotateY(-90deg) translateZ(-$half); }
@keyframes look-around {
from { transform: rotateY(-35deg); }
to { transform: rotateY(325deg); }
}
Where does this break?
All nine lean on the same three properties for their sense of depth, and the three didn't land everywhere at the same time. perspective reached Baseline "Widely available" back in 2015, and transform-style: preserve-3d reached that same status on the same timeline according to MDN. backface-visibility — the one this set leans on hardest, since six of the nine items depend on it — only crossed that line in March 2022, years after the other two. caniuse's CSS 3D Transforms table shows all three running in every current major browser now, so the gap only matters for a project still supporting very old releases.
Two of the nine also hide a trap that only shows up once the code actually runs. Item 02 places its five cards with rotateY(#{$i} * #{$step}deg), an SCSS string built by dropping two variables straight into the value. For the first card, Sass hands the browser rotateY(0 * 72deg) — and a transform function reading that outside of calc() can't multiply it, so the whole declaration is thrown out as invalid, and every card lands stacked on the same spot instead of spread around the ring. Multiplying ($i - 1) * $step inside Sass first, before the value ever reaches the compiled CSS, hands the browser a finished angle like 72deg it can actually parse. Item 06's coverflow hides a quieter version of the same lesson: z-index sits inside its shared keyframes next to opacity and scale, but z-index isn't an animatable property, so it snaps at each keyframe boundary instead of interpolating between them — the smooth-looking overlap a viewer sees comes entirely from opacity and scale shifting together, not from z-index easing anywhere at all. Every one of the nine also keeps backface-visibility: hidden next to a transform-style: preserve-3d set on an actual ancestor in the chain, since the hidden face does nothing once any parent along that chain drops out of the 3D context and flattens its children back down to a plane. The nine SCSS files with every one of those fixes already in place unlock from the zip below with bfmwjndp, typed exactly as this line spells it, no capitals added anywhere.
Accessibility
All nine drop to a fixed end state under prefers-reduced-motion: reduce rather than disappearing outright, so the content stays visible with the motion gone. Items 01, 02, and 04 through 09 turn their animation off and settle at a fixed viewing angle — a cube caught mid-turn, a room facing forward — so nothing snaps back to a blank starting frame. Item 03 is the one exception: reduced motion clears its transition and its transform back to none entirely, since a hover card stuck holding a tilted angle with no way to reach it again would read as broken rather than restrained. That same item is also the one built for a keyboard from the start — its trigger carries tabindex="0" so :focus-visible lands the identical fixed angle a mouse hover gets, with nothing here depending on a pointer being present at all.
@media (prefers-reduced-motion: reduce) {
.cube { animation: none; transform: rotateY(-28deg); }
.ring { animation: none; transform: rotateY(0deg); }
.tilt, .tilt.is-demo { animation: none; transition: none; transform: none; }
.book__cover { animation: none; transform: rotateY(-150deg); }
.gallery.is-demo .tile__inner { animation: none; transform: rotateY(180deg); }
.flow__card { animation: none; transform: translateX(0) translateZ(0) rotateY(0deg) scale(1); opacity: 1; }
.turntable__object { animation: none; transform: rotateY(-20deg); }
.sign { animation: none; transform: rotateX(-20deg); }
.room { animation: none; transform: rotateY(-15deg); }
}
The rest of this line of demos lives on the CSS category hub, and what this site does and doesn't sell is covered on the about page.
FAQ
Can I use just one css 3d cube effect without the other eight?
Yes — each item's SCSS sits in its own file under its own class names, so item 01's cube copies over on its own without pulling in the ring, the tilt, or anything else in this set. The three shared properties only need to sit on that cube's own markup, not on a page-wide wrapper shared by all nine.
Do any of these nine 3D effects need JavaScript to run?
No — all nine, including the hover tilt in item 03, run entirely on a CSS animation or transition once a browser supports the three properties covered above. The only place a script shows up at all is the .is-demo class the live grid uses to auto-loop every effect for a viewer who can't hover a fixed iframe; the standalone files in the zip drop that class and answer to a real cursor, a keyboard, or a tap instead.
Why does rotateY(0 * 72deg) break the 3D carousel instead of just rotating by zero?
Because Sass interpolates #{$i} * #{$step} as a literal string rather than doing the multiplication itself, the browser ends up reading rotateY(0 * 72deg) as a raw value in its transform list, and outside of calc() that list doesn't evaluate arithmetic written that way — so the whole declaration is dropped rather than rounded to zero. Multiplying inside Sass before the value reaches the compiled CSS avoids the issue completely.