Pure CSS Cards & galleries #loop

Ring carousel in pure CSS

Panels arranged on a circle. The radius comes from tan(): (width / 2) / tan(180° / count).

  • 35 lines of CSS
  • 8 lines of HTML
  • No JavaScript
  • No dependencies
  • Snippet under MIT
GitHub

How it works

  1. Every panel sits in the same spot, then gets rotateY(n × 360° / count) followed by translateZ(radius).
  2. Order matters: rotate first, then translate — so each panel moves outward along its own rotated axis.
  3. Radius for a closed ring: (width / 2) / tan(180° / count). For 8 panels 80 units wide that is ≈ 97 units; add a little for gaps.
  4. One animation on the ring does both: it spins a full turn and holds rotateX(-14deg) throughout, so you look slightly down on it.
  5. Those units are one base unit, --u, tied to the canvas: the panel, the radius and the perspective are all multiples of it, so the ring is the same share of a gallery card, the editor and a recording canvas. Only the radius decides the width, so it is what the band is sized against.

Key techniques

  • preserve-3d
  • rotateY + translateZ
  • radius = (width / 2) / tan(180° / n)

Copy-paste code

HTML 8 lines

<div class="scene">
  <div class="ring">
    <div style="--i:0">1</div><div style="--i:1">2</div>
    <div style="--i:2">3</div><div style="--i:3">4</div>
    <div style="--i:4">5</div><div style="--i:5">6</div>
    <div style="--i:6">7</div><div style="--i:7">8</div>
  </div>
</div>

CSS 35 lines

.scene {
  /* one base unit: every length below is a multiple of it, so the ring is the same share of a
     card, the editor, a full screen and a recording canvas */
  --u: 0.34vmin;
  perspective: calc(900 * var(--u));
}

.ring {
  --count: 8;
  --radius: calc(115 * var(--u));
  position: relative;
  width: calc(80 * var(--u));
  height: calc(110 * var(--u));
  transform-style: preserve-3d;
  animation: ring-spin 14s linear infinite;
}

.ring > div {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  border-radius: calc(10 * var(--u));
  font: 800 calc(22 * var(--u)) Inter, system-ui;
  color: #fff;
  background: hsl(calc(250 + var(--i) * 18) 80% 52% / 0.92); /* deep enough for white numbers on either stage */
  transform:
    rotateY(calc(var(--i) * 360deg / var(--count)))
    translateZ(var(--radius));
}

@keyframes ring-spin {
  from { transform: rotateX(-14deg) rotateY(0deg); }
  to   { transform: rotateX(-14deg) rotateY(-360deg); }
}
Standalone snippet · plain CSS, no build step, no dependencies.