22 CSS Image Galleries05 / 22

Pure CSSMIT licensed

CSS Radial Orbit Image Gallery

Circular thumbnails arranged evenly around a central hero and slowly revolving around it like planets — a radial orbit gallery for 'our services', galleries and playful hero sections. Pure CSS transforms, pauses on hover.

Published

Live Demo
Try it

The code

<section class="gl-17" aria-label="Radial orbit gallery">
  <div class="gl-17__system">
    <div class="gl-17__center"><img src="https://loremflickr.com/300/300/galaxy,space?lock=1700" alt="Featured central image" loading="lazy" decoding="async"></div>
    <div class="gl-17__ring">
      <a class="gl-17__sat" href="#" style="--a:0deg"><img src="https://loremflickr.com/200/200/planet?lock=1701" alt="Orbit photo 1" loading="lazy" decoding="async"></a>
      <a class="gl-17__sat" href="#" style="--a:45deg"><img src="https://loremflickr.com/200/200/moon?lock=1702" alt="Orbit photo 2" loading="lazy" decoding="async"></a>
      <a class="gl-17__sat" href="#" style="--a:90deg"><img src="https://loremflickr.com/200/200/star?lock=1703" alt="Orbit photo 3" loading="lazy" decoding="async"></a>
      <a class="gl-17__sat" href="#" style="--a:135deg"><img src="https://loremflickr.com/200/200/nebula?lock=1704" alt="Orbit photo 4" loading="lazy" decoding="async"></a>
      <a class="gl-17__sat" href="#" style="--a:180deg"><img src="https://loremflickr.com/200/200/comet?lock=1705" alt="Orbit photo 5" loading="lazy" decoding="async"></a>
      <a class="gl-17__sat" href="#" style="--a:225deg"><img src="https://loremflickr.com/200/200/sky,night?lock=1706" alt="Orbit photo 6" loading="lazy" decoding="async"></a>
      <a class="gl-17__sat" href="#" style="--a:270deg"><img src="https://loremflickr.com/200/200/telescope?lock=1707" alt="Orbit photo 7" loading="lazy" decoding="async"></a>
      <a class="gl-17__sat" href="#" style="--a:315deg"><img src="https://loremflickr.com/200/200/astronomy?lock=1708" alt="Orbit photo 8" loading="lazy" decoding="async"></a>
    </div>
  </div>
</section>
.gl-17,
.gl-17 *,
.gl-17 *::before,
.gl-17 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.gl-17 ::selection {
  background: oklch(0.7 0.16 270);
  color: #fff;
}

.gl-17 {
  --r: 170px;
  --accent: oklch(0.72 0.16 270);
  font-family: 'Segoe UI',system-ui,sans-serif;
  width: 100%;
  min-height: 100vh;
  padding: 40px 20px;
  background: radial-gradient(120% 120% at 50% 40%,#1a1836,#06060f);
  display: flex;
  align-items: center;
  justify-content: center;
}

.gl-17__system {
  position: relative;
  width: calc(var(--r) * 2 + 90px);
  height: calc(var(--r) * 2 + 90px);
  display: flex;
  align-items: center;
  justify-content: center;
}

.gl-17__center {
  width: 120px;
  height: 120px;
  border-radius: 50%;
  overflow: hidden;
  z-index: 2;
  box-shadow: 0 0 40px -6px var(--accent),0 0 0 4px rgba(255,255,255,.1);
}

.gl-17__center img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.gl-17__ring {
  position: absolute;
  inset: 0;
  animation: gl-17-spin 26s linear infinite;
}

.gl-17__system:hover .gl-17__ring,
.gl-17__system:focus-within .gl-17__ring {
  animation-play-state: paused;
}

.gl-17__sat {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 66px;
  height: 66px;
  margin: -33px;
  border-radius: 50%;
  overflow: hidden;
  text-decoration: none;
  transform: rotate(var(--a)) translate(var(--r)) rotate(calc(-1 * var(--a)));
  box-shadow: 0 0 0 3px rgba(255,255,255,.12),0 8px 20px -8px rgba(0,0,0,.6);
  animation: gl-17-spin 26s linear infinite reverse;
  transition: box-shadow .3s;
}

.gl-17__sat img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform .3s;
}

.gl-17__sat:hover img,
.gl-17__sat:focus-visible img {
  transform: scale(1.12);
}

.gl-17__sat:hover,
.gl-17__sat:focus-visible {
  box-shadow: 0 0 0 3px var(--accent),0 10px 26px -8px rgba(0,0,0,.7);
}

.gl-17__sat:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 3px;
}

@keyframes gl-17-spin {
  to {
    transform: rotate(360deg);
  }
}

@media (max-width: 560px) {
  .gl-17 {
    --r: 120px;
  }

  .gl-17__sat {
    width: 54px;
    height: 54px;
    margin: -27px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .gl-17__ring,
    .gl-17__sat {
    animation: none;
  }
}
/* No JavaScript — a revolving ring with counter-rotating thumbnails; pauses on hover. */
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Image Gallerie from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: CSS Radial Orbit Image Gallery
Source: https://codefronts.com/snippets/css-image-gallery/css-radial-orbit-image-gallery/

Circular thumbnails arranged evenly around a central hero and slowly revolving around it like planets — a radial orbit gallery for 'our services', galleries and playful hero sections. Pure CSS transforms, pauses on hover.
## HTML
```html
<section class="gl-17" aria-label="Radial orbit gallery">
  <div class="gl-17__system">
    <div class="gl-17__center"><img src="https://loremflickr.com/300/300/galaxy,space?lock=1700" alt="Featured central image" loading="lazy" decoding="async"></div>
    <div class="gl-17__ring">
      <a class="gl-17__sat" href="#" style="--a:0deg"><img src="https://loremflickr.com/200/200/planet?lock=1701" alt="Orbit photo 1" loading="lazy" decoding="async"></a>
      <a class="gl-17__sat" href="#" style="--a:45deg"><img src="https://loremflickr.com/200/200/moon?lock=1702" alt="Orbit photo 2" loading="lazy" decoding="async"></a>
      <a class="gl-17__sat" href="#" style="--a:90deg"><img src="https://loremflickr.com/200/200/star?lock=1703" alt="Orbit photo 3" loading="lazy" decoding="async"></a>
      <a class="gl-17__sat" href="#" style="--a:135deg"><img src="https://loremflickr.com/200/200/nebula?lock=1704" alt="Orbit photo 4" loading="lazy" decoding="async"></a>
      <a class="gl-17__sat" href="#" style="--a:180deg"><img src="https://loremflickr.com/200/200/comet?lock=1705" alt="Orbit photo 5" loading="lazy" decoding="async"></a>
      <a class="gl-17__sat" href="#" style="--a:225deg"><img src="https://loremflickr.com/200/200/sky,night?lock=1706" alt="Orbit photo 6" loading="lazy" decoding="async"></a>
      <a class="gl-17__sat" href="#" style="--a:270deg"><img src="https://loremflickr.com/200/200/telescope?lock=1707" alt="Orbit photo 7" loading="lazy" decoding="async"></a>
      <a class="gl-17__sat" href="#" style="--a:315deg"><img src="https://loremflickr.com/200/200/astronomy?lock=1708" alt="Orbit photo 8" loading="lazy" decoding="async"></a>
    </div>
  </div>
</section>
```
## CSS
```css
.gl-17,
.gl-17 *,
.gl-17 *::before,
.gl-17 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.gl-17 ::selection {
  background: oklch(0.7 0.16 270);
  color: #fff;
}

.gl-17 {
  --r: 170px;
  --accent: oklch(0.72 0.16 270);
  font-family: 'Segoe UI',system-ui,sans-serif;
  width: 100%;
  min-height: 100vh;
  padding: 40px 20px;
  background: radial-gradient(120% 120% at 50% 40%,#1a1836,#06060f);
  display: flex;
  align-items: center;
  justify-content: center;
}

.gl-17__system {
  position: relative;
  width: calc(var(--r) * 2 + 90px);
  height: calc(var(--r) * 2 + 90px);
  display: flex;
  align-items: center;
  justify-content: center;
}

.gl-17__center {
  width: 120px;
  height: 120px;
  border-radius: 50%;
  overflow: hidden;
  z-index: 2;
  box-shadow: 0 0 40px -6px var(--accent),0 0 0 4px rgba(255,255,255,.1);
}

.gl-17__center img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.gl-17__ring {
  position: absolute;
  inset: 0;
  animation: gl-17-spin 26s linear infinite;
}

.gl-17__system:hover .gl-17__ring,
.gl-17__system:focus-within .gl-17__ring {
  animation-play-state: paused;
}

.gl-17__sat {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 66px;
  height: 66px;
  margin: -33px;
  border-radius: 50%;
  overflow: hidden;
  text-decoration: none;
  transform: rotate(var(--a)) translate(var(--r)) rotate(calc(-1 * var(--a)));
  box-shadow: 0 0 0 3px rgba(255,255,255,.12),0 8px 20px -8px rgba(0,0,0,.6);
  animation: gl-17-spin 26s linear infinite reverse;
  transition: box-shadow .3s;
}

.gl-17__sat img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform .3s;
}

.gl-17__sat:hover img,
.gl-17__sat:focus-visible img {
  transform: scale(1.12);
}

.gl-17__sat:hover,
.gl-17__sat:focus-visible {
  box-shadow: 0 0 0 3px var(--accent),0 10px 26px -8px rgba(0,0,0,.7);
}

.gl-17__sat:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 3px;
}

@keyframes gl-17-spin {
  to {
    transform: rotate(360deg);
  }
}

@media (max-width: 560px) {
  .gl-17 {
    --r: 120px;
  }

  .gl-17__sat {
    width: 54px;
    height: 54px;
    margin: -27px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .gl-17__ring,
    .gl-17__sat {
    animation: none;
  }
}
```

## JavaScript
```js
/* No JavaScript — a revolving ring with counter-rotating thumbnails; pauses on hover. */
```

How this works

Each orbiting item is absolutely centred, then pushed out along a radius with transform: rotate(var(--a)) translate(radius) rotate(calc(-1 * var(--a))) — the first rotation sets its angle on the ring, the translate moves it out, and the counter-rotation keeps the thumbnail upright. A wrapping ring element runs a linear @keyframes spin, and each thumbnail runs the same animation in reverse so images never appear to tumble.

The orbit pauses on :hover/:focus-within so users can aim at a thumbnail, and the whole animation is disabled under prefers-reduced-motion.

Make it yours

  • Add/remove satellites by changing the per-item --a angles (360 ÷ count).
  • Grow the orbit by editing the translate radius.
  • Speed or reverse the revolution via the spin animation-duration/direction.
  • Swap the centre hero for a logo, title or call-to-action.

Gotchas — read before shipping

  • Counter-rotate each thumbnail (and reverse its animation) or the images spin end-over-end as the ring turns.
  • Pause on hover/focus so the moving targets are actually clickable.
  • Honour prefers-reduced-motion — a constantly revolving ring is a common motion-sensitivity trigger.

Browser support

ChromeSafariFirefoxEdge
111+15.4+113+111+

Floor set by oklch() as this demo is written. The core technique itself goes back further — Chrome 43+.

CSS transforms + keyframes — supported everywhere; the counter-spin technique needs no modern features.

Techniques used in this demo

Search CodeFronts

Loading…