3D Carousel
Six small 80 × 60 px gradient tiles turn in a ring around a vertical axis, one turn every 10 s; their back sides are hidden, so you only see the two or three tiles facing you. The ring stops on hover, on keyboard focus and on a finger tap, so you can read the tile in front. Pure CSS, zero JavaScript, still when the user reduces motion.
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. 3D has 50 effects, including 8 free. Explore the category →
3 usage examples



How it works
The .fx-0004-root root (#0a0a0f background, at least 300 px tall) holds a 280 × 180 px .rcar-stage that carries the camera (perspective: 800px). The .rcar-ring is only 80 × 60 px, the size of one tile: it keeps its children in 3D (transform-style: preserve-3d) and turns from rotateY(0) to rotateY(360deg) in 10 s, linear, forever (the rcarSpin animation). The six .rcar-tile elements are stacked at the center, then placed with :nth-child: rotateY(0deg), 60deg… 300deg, each followed by translateZ(100px). Each tile has its own 135° gradient and 0.75 rem rounded corners.
Every tile has backface-visibility: hidden: as soon as a tile goes past 90° and shows its back, it disappears. With six tiles 60° apart on a 100 px radius, you always see the front tile and its two neighbors at an angle, with gaps behind: the ring reads without a clutter of mirrored tiles. The pause is one rule: .rcar-ring:hover and .rcar-stage:focus .rcar-ring set animation-play-state: paused. The stage has tabindex="0": the Tab key reaches it (2 px #a5b4fc outline on keyboard focus), and a finger tap gives it focus too. Measured in Chromium 151 and Firefox 153: 8.1% of pixels change over 0.8 s while the ring turns, 0% on hover, 0% on keyboard focus, 0% after a touch tap; it resumes when the pointer leaves or focus moves elsewhere.
The classes use the rcar- prefix rather than .carousel and .carousel-item: those are Bootstrap's names, and its .carousel-item { display: none } rule would hide the tiles on a site that loads it. The animation only touches transform (handled by the compositor). Two copies on one page turn and pause independently. Playwright's WebKit flattens all CSS 3D, even a reference cube: rendering on a real Safari was not verified.
Accessibility
- Reduced motion: under
prefers-reduced-motion: reduce, a block scoped to.fx-0004-rootremoves the animation: the ring stays still, tile 1 facing front and its two neighbors at an angle. Measured in Chromium and Firefox: 0 running animations, 0% of pixels changing over 0.8 s. - Keyboard and touch: the Tab key reaches the stage and the ring stops while it has focus (outline visible with the keyboard); on touch screens, a tap stops it and a tap elsewhere restarts it. Rewrite the group label (
aria-label) in your page's language. - Contrast: the 12 px white labels reach 4.5:1 on indigo, but drop to 2.2:1 on amber and 2.4:1 on cyan, below the 4.5:1 threshold for text this size. Darken those gradients or set the text to 18 px bold.
- Content: a tile that has turned to the back is hidden, so it leaves the screen for a moment, but its text stays in the HTML and a screen reader reads all six. If the tiles lead somewhere, make them links and keep the list readable elsewhere: a turning tile is a moving target.
Browser compatibility
CSS (3D transform, preserve-3d, backface-visibility, perspective, animation-play-state), zero JavaScript, no dependencies. Measured in Chromium 151 and Firefox 153: same ring (0.26% difference on the still frame), pause on hover, focus and touch, two independent copies, 0 errors. Playwright's WebKit 26.5 flattens all CSS 3D: the real Safari was not verified.
Without preserve-3d, the six tiles collapse at the center of the stage and overlap; without backface-visibility, the far tiles show mirrored. The tiles' text stays in the page.
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0004-root">
<div class="demo-3d">
<div class="rcar-stage" tabindex="0" role="group" aria-label="…">
<div class="rcar-ring">
<div class="rcar-tile">Item 1</div> … (6 tiles)
</div>
</div>
</div>
</div>
One-time payment, no subscription — the code is yours forever
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Speed (CSS — 10s in the animation of .rcar-ring) |
10 s per turn | 15 to 20 s if the tiles carry text to read; 6 s for a plain ornament. Keep linear. |
Radius (CSS — translateZ(100px) in the six .rcar-tile:nth-child(n) rules) |
100 px | Larger (140 px), the tiles spread out and the ring widens: widen .rcar-stage too. Below 70 px, six 80 px tiles overlap. |
| Number of tiles (HTML and CSS) | 6, every 60° | For 8 tiles: one angle every 45° and a radius of at least 100 px. Each tile has its own :nth-child rule (angle and gradient). |
Back side (CSS — backface-visibility of .rcar-tile) |
hidden | visible also shows the far tiles, mirrored: a full ring but reversed text. Keep hidden as soon as the tiles carry words. |
Pause (CSS — the .rcar-ring:hover, .rcar-stage:focus .rcar-ring rule) |
hover, keyboard focus, finger tap | To let only the keyboard and hover stop it (not a mouse click), add .rcar-stage:focus:not(:focus-visible) .rcar-ring { animation-play-state: running }, as in the scenes above. To never stop it, delete the rule and the tabindex. |
Tile size (CSS — width / height of .rcar-ring and .rcar-tile) |
80 × 60 px | Change both rules together. For long labels, 100 × 70 px with a 120 px radius. |
FAQ
backface-visibility: hidden): a tile turned past 90° shows its back and disappears. You see the front tile and its two neighbors; the others reappear as they come back around. That is intended: words never show up mirrored on screen.tabindex="0": a finger tap gives it focus, and the .rcar-stage:focus .rcar-ring rule pauses the ring (measured: 0% of pixels changing after the tap). A tap elsewhere on the page removes focus and it starts again..carousel and .carousel-item are Bootstrap classes. On a site that loads Bootstrap, its .carousel-item { display: none } rule would hide the tiles, and its transitions would mix into the ring. The rcar- prefix avoids the clash; keep a prefix if you rename.