05 / 22
All 22 in 22 Tailwind CSS Image Gallery
- 01Tailwind Hexagon Honeycomb Image Gallery with clip-path
- 02Tailwind Justified Image Gallery with Aspect-Ratio Flex Rows
- 03Tailwind Expanding Panel Image Gallery with Flex-Grow Transitions
- 04Tailwind :has() Hover Spotlight Image Grid
- 05Tailwind Radial Orbit Image Gallery with Counter-Rotating Thumbnails
- 06Tailwind Interactive Pointer Trail Image Gallery
- 07Tailwind Masonry Image Gallery with CSS Columns (No JavaScript)
- 08Tailwind Metro Tile Grid Gallery with Dense Auto-Flow
- 09Tailwind Editorial Magazine Grid Gallery with Blend-Mode Headline
- 10Tailwind Pure CSS Lightbox Gallery with :target (No JavaScript)
- 11Tailwind Filmstrip Carousel Gallery with CSS Scroll-Snap
- 12Tailwind Accordion Image Gallery with Flex-Grow on Hover & Focus
- 13Tailwind Mosaic Grid Gallery with Contained Zoom on Hover
- 14Tailwind Glassmorphism Image Cards with backdrop-blur
- 15Tailwind Diagonal Slice Reveal Gallery with clip-path Halves
- 16Tailwind 3D Tilt Parallax Image Cards with Pointer-Driven Variables
- 17Tailwind 3D Cube Image Viewer with Radio-Driven Rotation
- 18Tailwind 3D Perspective Depth Stack Gallery That Fans on Focus
- 19Tailwind Infinite Scrolling Image Marquee with Seamless Loop
- 20Tailwind Ken Burns Slideshow Gallery with Staggered Cross-Fade
- 21Tailwind Polaroid Photo Stack Gallery with Rotate-on-Hover
- 22Tailwind Scattered Photo Collage with :target Spotlight
Tailwind Radial Orbit Image Gallery with Counter-Rotating Thumbnails
Circular thumbnails spaced evenly around a central hero, revolving slowly like planets and pausing the moment you reach for one. The port's interesting move is that the ring animation becomes a real animate-* utility through @theme, while the per-satellite angle stays inline data.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tgl-05 w-full min-h-svh grid place-items-center px-5 py-10 font-tgl05-sans bg-[radial-gradient(120%_120%_at_50%_40%,oklch(0.26_0.06_290),oklch(0.09_0.02_280))] [--r:170px] max-[560px]:[--r:120px]" aria-label="Radial orbit gallery">
<div class="group relative grid place-items-center w-[calc(var(--r)*2+90px)] h-[calc(var(--r)*2+90px)]">
<div class="w-[120px] h-[120px] rounded-full overflow-hidden z-[2] shadow-[0_0_40px_-6px_var(--color-tgl05-acc),0_0_0_4px_rgba(255,255,255,0.1)]">
<img src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?q=80&w=300&h=300&auto=format&fit=crop" alt="Spiral galaxy, featured image" loading="lazy" decoding="async" class="w-full h-full object-cover">
</div>
<div class="absolute inset-0 animate-tgl05-spin group-hover:[animation-play-state:paused] group-focus-within:[animation-play-state:paused] motion-reduce:animate-none">
<a href="#" style="--a:0deg" class="group/sat absolute top-1/2 left-1/2 w-[66px] h-[66px] -m-[33px] rounded-full overflow-hidden no-underline animate-tgl05-spin [animation-direction:reverse] [transform:rotate(var(--a))_translate(var(--r))_rotate(calc(-1*var(--a)))] shadow-[0_0_0_3px_rgba(255,255,255,0.12),0_8px_20px_-8px_rgba(0,0,0,0.6)] transition-shadow duration-300 hover:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:outline-3 focus-visible:outline-tgl05-acc focus-visible:outline-offset-[3px] motion-reduce:animate-none max-[560px]:w-[54px] max-[560px]:h-[54px] max-[560px]:-m-[27px]">
<img src="https://images.unsplash.com/photo-1502672260266-1c1ef2d93688?q=80&w=200&h=200&auto=format&fit=crop" alt="Planet surface" loading="lazy" decoding="async" class="w-full h-full object-cover transition-transform duration-300 group-hover/sat:scale-[1.12] motion-reduce:transition-none">
</a>
<a href="#" style="--a:45deg" class="group/sat absolute top-1/2 left-1/2 w-[66px] h-[66px] -m-[33px] rounded-full overflow-hidden no-underline animate-tgl05-spin [animation-direction:reverse] [transform:rotate(var(--a))_translate(var(--r))_rotate(calc(-1*var(--a)))] shadow-[0_0_0_3px_rgba(255,255,255,0.12),0_8px_20px_-8px_rgba(0,0,0,0.6)] transition-shadow duration-300 hover:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:outline-3 focus-visible:outline-tgl05-acc focus-visible:outline-offset-[3px] motion-reduce:animate-none max-[560px]:w-[54px] max-[560px]:h-[54px] max-[560px]:-m-[27px]">
<img src="https://images.unsplash.com/photo-1620121692029-d088224ddc74?q=80&w=200&h=200&auto=format&fit=crop" alt="Full moon" loading="lazy" decoding="async" class="w-full h-full object-cover transition-transform duration-300 group-hover/sat:scale-[1.12] motion-reduce:transition-none">
</a>
<a href="#" style="--a:90deg" class="group/sat absolute top-1/2 left-1/2 w-[66px] h-[66px] -m-[33px] rounded-full overflow-hidden no-underline animate-tgl05-spin [animation-direction:reverse] [transform:rotate(var(--a))_translate(var(--r))_rotate(calc(-1*var(--a)))] shadow-[0_0_0_3px_rgba(255,255,255,0.12),0_8px_20px_-8px_rgba(0,0,0,0.6)] transition-shadow duration-300 hover:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:outline-3 focus-visible:outline-tgl05-acc focus-visible:outline-offset-[3px] motion-reduce:animate-none max-[560px]:w-[54px] max-[560px]:h-[54px] max-[560px]:-m-[27px]">
<img src="https://images.unsplash.com/photo-1524995997946-a1c2e315a42f?q=80&w=200&h=200&auto=format&fit=crop" alt="Bright star field" loading="lazy" decoding="async" class="w-full h-full object-cover transition-transform duration-300 group-hover/sat:scale-[1.12] motion-reduce:transition-none">
</a>
<a href="#" style="--a:135deg" class="group/sat absolute top-1/2 left-1/2 w-[66px] h-[66px] -m-[33px] rounded-full overflow-hidden no-underline animate-tgl05-spin [animation-direction:reverse] [transform:rotate(var(--a))_translate(var(--r))_rotate(calc(-1*var(--a)))] shadow-[0_0_0_3px_rgba(255,255,255,0.12),0_8px_20px_-8px_rgba(0,0,0,0.6)] transition-shadow duration-300 hover:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:outline-3 focus-visible:outline-tgl05-acc focus-visible:outline-offset-[3px] motion-reduce:animate-none max-[560px]:w-[54px] max-[560px]:h-[54px] max-[560px]:-m-[27px]">
<img src="https://images.unsplash.com/photo-1616486338812-3dadae4b4ace?q=80&w=200&h=200&auto=format&fit=crop" alt="Coloured nebula" loading="lazy" decoding="async" class="w-full h-full object-cover transition-transform duration-300 group-hover/sat:scale-[1.12] motion-reduce:transition-none">
</a>
<a href="#" style="--a:180deg" class="group/sat absolute top-1/2 left-1/2 w-[66px] h-[66px] -m-[33px] rounded-full overflow-hidden no-underline animate-tgl05-spin [animation-direction:reverse] [transform:rotate(var(--a))_translate(var(--r))_rotate(calc(-1*var(--a)))] shadow-[0_0_0_3px_rgba(255,255,255,0.12),0_8px_20px_-8px_rgba(0,0,0,0.6)] transition-shadow duration-300 hover:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:outline-3 focus-visible:outline-tgl05-acc focus-visible:outline-offset-[3px] motion-reduce:animate-none max-[560px]:w-[54px] max-[560px]:h-[54px] max-[560px]:-m-[27px]">
<img src="https://images.unsplash.com/photo-1517180102446-f3ece451e9d8?q=80&w=200&h=200&auto=format&fit=crop" alt="Comet trail" loading="lazy" decoding="async" class="w-full h-full object-cover transition-transform duration-300 group-hover/sat:scale-[1.12] motion-reduce:transition-none">
</a>
<a href="#" style="--a:225deg" class="group/sat absolute top-1/2 left-1/2 w-[66px] h-[66px] -m-[33px] rounded-full overflow-hidden no-underline animate-tgl05-spin [animation-direction:reverse] [transform:rotate(var(--a))_translate(var(--r))_rotate(calc(-1*var(--a)))] shadow-[0_0_0_3px_rgba(255,255,255,0.12),0_8px_20px_-8px_rgba(0,0,0,0.6)] transition-shadow duration-300 hover:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:outline-3 focus-visible:outline-tgl05-acc focus-visible:outline-offset-[3px] motion-reduce:animate-none max-[560px]:w-[54px] max-[560px]:h-[54px] max-[560px]:-m-[27px]">
<img src="https://images.unsplash.com/photo-1508700115892-45ecd05ae2ad?q=80&w=200&h=200&auto=format&fit=crop" alt="Night sky" loading="lazy" decoding="async" class="w-full h-full object-cover transition-transform duration-300 group-hover/sat:scale-[1.12] motion-reduce:transition-none">
</a>
<a href="#" style="--a:270deg" class="group/sat absolute top-1/2 left-1/2 w-[66px] h-[66px] -m-[33px] rounded-full overflow-hidden no-underline animate-tgl05-spin [animation-direction:reverse] [transform:rotate(var(--a))_translate(var(--r))_rotate(calc(-1*var(--a)))] shadow-[0_0_0_3px_rgba(255,255,255,0.12),0_8px_20px_-8px_rgba(0,0,0,0.6)] transition-shadow duration-300 hover:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:outline-3 focus-visible:outline-tgl05-acc focus-visible:outline-offset-[3px] motion-reduce:animate-none max-[560px]:w-[54px] max-[560px]:h-[54px] max-[560px]:-m-[27px]">
<img src="https://images.unsplash.com/photo-1590490360182-c33d57733427?q=80&w=200&h=200&auto=format&fit=crop" alt="Observatory telescope" loading="lazy" decoding="async" class="w-full h-full object-cover transition-transform duration-300 group-hover/sat:scale-[1.12] motion-reduce:transition-none">
</a>
<a href="#" style="--a:315deg" class="group/sat absolute top-1/2 left-1/2 w-[66px] h-[66px] -m-[33px] rounded-full overflow-hidden no-underline animate-tgl05-spin [animation-direction:reverse] [transform:rotate(var(--a))_translate(var(--r))_rotate(calc(-1*var(--a)))] shadow-[0_0_0_3px_rgba(255,255,255,0.12),0_8px_20px_-8px_rgba(0,0,0,0.6)] transition-shadow duration-300 hover:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:outline-3 focus-visible:outline-tgl05-acc focus-visible:outline-offset-[3px] motion-reduce:animate-none max-[560px]:w-[54px] max-[560px]:h-[54px] max-[560px]:-m-[27px]">
<img src="https://images.unsplash.com/photo-1444723121867-7a241cacace9?q=80&w=200&h=200&auto=format&fit=crop" alt="Star chart detail" loading="lazy" decoding="async" class="w-full h-full object-cover transition-transform duration-300 group-hover/sat:scale-[1.12] motion-reduce:transition-none">
</a>
</div>
</div>
</section><section class="tgl-05 w-full min-h-svh grid place-items-center px-5 py-10 font-tgl05-sans bg-[radial-gradient(120%_120%_at_50%_40%,oklch(0.26_0.06_290),oklch(0.09_0.02_280))] [--r:170px] max-[560px]:[--r:120px]" aria-label="Radial orbit gallery">
<div class="group relative grid place-items-center w-[calc(var(--r)*2+90px)] h-[calc(var(--r)*2+90px)]">
<div class="w-[120px] h-[120px] rounded-full overflow-hidden z-[2] shadow-[0_0_40px_-6px_var(--color-tgl05-acc),0_0_0_4px_rgba(255,255,255,0.1)]">
<img src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?q=80&w=300&h=300&auto=format&fit=crop" alt="Spiral galaxy, featured image" loading="lazy" decoding="async" class="w-full h-full object-cover">
</div>
<div class="absolute inset-0 animate-tgl05-spin group-hover:[animation-play-state:paused] group-focus-within:[animation-play-state:paused] motion-reduce:animate-none">
<a href="#" style="--a:0deg" class="group/sat absolute top-1/2 left-1/2 w-[66px] h-[66px] -m-[33px] rounded-full overflow-hidden no-underline animate-tgl05-spin [animation-direction:reverse] [transform:rotate(var(--a))_translate(var(--r))_rotate(calc(-1*var(--a)))] shadow-[0_0_0_3px_rgba(255,255,255,0.12),0_8px_20px_-8px_rgba(0,0,0,0.6)] transition-shadow duration-300 hover:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:outline-3 focus-visible:outline-tgl05-acc focus-visible:outline-offset-[3px] motion-reduce:animate-none max-[560px]:w-[54px] max-[560px]:h-[54px] max-[560px]:-m-[27px]">
<img src="https://images.unsplash.com/photo-1502672260266-1c1ef2d93688?q=80&w=200&h=200&auto=format&fit=crop" alt="Planet surface" loading="lazy" decoding="async" class="w-full h-full object-cover transition-transform duration-300 group-hover/sat:scale-[1.12] motion-reduce:transition-none">
</a>
<a href="#" style="--a:45deg" class="group/sat absolute top-1/2 left-1/2 w-[66px] h-[66px] -m-[33px] rounded-full overflow-hidden no-underline animate-tgl05-spin [animation-direction:reverse] [transform:rotate(var(--a))_translate(var(--r))_rotate(calc(-1*var(--a)))] shadow-[0_0_0_3px_rgba(255,255,255,0.12),0_8px_20px_-8px_rgba(0,0,0,0.6)] transition-shadow duration-300 hover:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:outline-3 focus-visible:outline-tgl05-acc focus-visible:outline-offset-[3px] motion-reduce:animate-none max-[560px]:w-[54px] max-[560px]:h-[54px] max-[560px]:-m-[27px]">
<img src="https://images.unsplash.com/photo-1620121692029-d088224ddc74?q=80&w=200&h=200&auto=format&fit=crop" alt="Full moon" loading="lazy" decoding="async" class="w-full h-full object-cover transition-transform duration-300 group-hover/sat:scale-[1.12] motion-reduce:transition-none">
</a>
<a href="#" style="--a:90deg" class="group/sat absolute top-1/2 left-1/2 w-[66px] h-[66px] -m-[33px] rounded-full overflow-hidden no-underline animate-tgl05-spin [animation-direction:reverse] [transform:rotate(var(--a))_translate(var(--r))_rotate(calc(-1*var(--a)))] shadow-[0_0_0_3px_rgba(255,255,255,0.12),0_8px_20px_-8px_rgba(0,0,0,0.6)] transition-shadow duration-300 hover:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:outline-3 focus-visible:outline-tgl05-acc focus-visible:outline-offset-[3px] motion-reduce:animate-none max-[560px]:w-[54px] max-[560px]:h-[54px] max-[560px]:-m-[27px]">
<img src="https://images.unsplash.com/photo-1524995997946-a1c2e315a42f?q=80&w=200&h=200&auto=format&fit=crop" alt="Bright star field" loading="lazy" decoding="async" class="w-full h-full object-cover transition-transform duration-300 group-hover/sat:scale-[1.12] motion-reduce:transition-none">
</a>
<a href="#" style="--a:135deg" class="group/sat absolute top-1/2 left-1/2 w-[66px] h-[66px] -m-[33px] rounded-full overflow-hidden no-underline animate-tgl05-spin [animation-direction:reverse] [transform:rotate(var(--a))_translate(var(--r))_rotate(calc(-1*var(--a)))] shadow-[0_0_0_3px_rgba(255,255,255,0.12),0_8px_20px_-8px_rgba(0,0,0,0.6)] transition-shadow duration-300 hover:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:outline-3 focus-visible:outline-tgl05-acc focus-visible:outline-offset-[3px] motion-reduce:animate-none max-[560px]:w-[54px] max-[560px]:h-[54px] max-[560px]:-m-[27px]">
<img src="https://images.unsplash.com/photo-1616486338812-3dadae4b4ace?q=80&w=200&h=200&auto=format&fit=crop" alt="Coloured nebula" loading="lazy" decoding="async" class="w-full h-full object-cover transition-transform duration-300 group-hover/sat:scale-[1.12] motion-reduce:transition-none">
</a>
<a href="#" style="--a:180deg" class="group/sat absolute top-1/2 left-1/2 w-[66px] h-[66px] -m-[33px] rounded-full overflow-hidden no-underline animate-tgl05-spin [animation-direction:reverse] [transform:rotate(var(--a))_translate(var(--r))_rotate(calc(-1*var(--a)))] shadow-[0_0_0_3px_rgba(255,255,255,0.12),0_8px_20px_-8px_rgba(0,0,0,0.6)] transition-shadow duration-300 hover:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:outline-3 focus-visible:outline-tgl05-acc focus-visible:outline-offset-[3px] motion-reduce:animate-none max-[560px]:w-[54px] max-[560px]:h-[54px] max-[560px]:-m-[27px]">
<img src="https://images.unsplash.com/photo-1517180102446-f3ece451e9d8?q=80&w=200&h=200&auto=format&fit=crop" alt="Comet trail" loading="lazy" decoding="async" class="w-full h-full object-cover transition-transform duration-300 group-hover/sat:scale-[1.12] motion-reduce:transition-none">
</a>
<a href="#" style="--a:225deg" class="group/sat absolute top-1/2 left-1/2 w-[66px] h-[66px] -m-[33px] rounded-full overflow-hidden no-underline animate-tgl05-spin [animation-direction:reverse] [transform:rotate(var(--a))_translate(var(--r))_rotate(calc(-1*var(--a)))] shadow-[0_0_0_3px_rgba(255,255,255,0.12),0_8px_20px_-8px_rgba(0,0,0,0.6)] transition-shadow duration-300 hover:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:outline-3 focus-visible:outline-tgl05-acc focus-visible:outline-offset-[3px] motion-reduce:animate-none max-[560px]:w-[54px] max-[560px]:h-[54px] max-[560px]:-m-[27px]">
<img src="https://images.unsplash.com/photo-1508700115892-45ecd05ae2ad?q=80&w=200&h=200&auto=format&fit=crop" alt="Night sky" loading="lazy" decoding="async" class="w-full h-full object-cover transition-transform duration-300 group-hover/sat:scale-[1.12] motion-reduce:transition-none">
</a>
<a href="#" style="--a:270deg" class="group/sat absolute top-1/2 left-1/2 w-[66px] h-[66px] -m-[33px] rounded-full overflow-hidden no-underline animate-tgl05-spin [animation-direction:reverse] [transform:rotate(var(--a))_translate(var(--r))_rotate(calc(-1*var(--a)))] shadow-[0_0_0_3px_rgba(255,255,255,0.12),0_8px_20px_-8px_rgba(0,0,0,0.6)] transition-shadow duration-300 hover:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:outline-3 focus-visible:outline-tgl05-acc focus-visible:outline-offset-[3px] motion-reduce:animate-none max-[560px]:w-[54px] max-[560px]:h-[54px] max-[560px]:-m-[27px]">
<img src="https://images.unsplash.com/photo-1590490360182-c33d57733427?q=80&w=200&h=200&auto=format&fit=crop" alt="Observatory telescope" loading="lazy" decoding="async" class="w-full h-full object-cover transition-transform duration-300 group-hover/sat:scale-[1.12] motion-reduce:transition-none">
</a>
<a href="#" style="--a:315deg" class="group/sat absolute top-1/2 left-1/2 w-[66px] h-[66px] -m-[33px] rounded-full overflow-hidden no-underline animate-tgl05-spin [animation-direction:reverse] [transform:rotate(var(--a))_translate(var(--r))_rotate(calc(-1*var(--a)))] shadow-[0_0_0_3px_rgba(255,255,255,0.12),0_8px_20px_-8px_rgba(0,0,0,0.6)] transition-shadow duration-300 hover:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:outline-3 focus-visible:outline-tgl05-acc focus-visible:outline-offset-[3px] motion-reduce:animate-none max-[560px]:w-[54px] max-[560px]:h-[54px] max-[560px]:-m-[27px]">
<img src="https://images.unsplash.com/photo-1444723121867-7a241cacace9?q=80&w=200&h=200&auto=format&fit=crop" alt="Star chart detail" loading="lazy" decoding="async" class="w-full h-full object-cover transition-transform duration-300 group-hover/sat:scale-[1.12] motion-reduce:transition-none">
</a>
</div>
</div>
</section>@import "tailwindcss";
@theme {
--color-tgl05-acc: #9b8cf0;
--color-tgl05-acc: oklch(0.72 0.16 285);
--font-tgl05-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tgl05-spin: tgl05-spin 26s linear infinite;
}
/* @keyframes has no utility form — it is the one thing that must stay CSS. */
@keyframes tgl05-spin {
to {
transform: rotate(360deg);
}
}
.tgl-05 {
width: 100%;
min-height: 100svh;
}
.tgl-05 ::selection {
background: var(--color-tgl05-acc);
color: #fff;
}@import "tailwindcss";
@theme {
--color-tgl05-acc: #9b8cf0;
--color-tgl05-acc: oklch(0.72 0.16 285);
--font-tgl05-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tgl05-spin: tgl05-spin 26s linear infinite;
}
/* @keyframes has no utility form — it is the one thing that must stay CSS. */
@keyframes tgl05-spin {
to {
transform: rotate(360deg);
}
}
.tgl-05 {
width: 100%;
min-height: 100svh;
}
.tgl-05 ::selection {
background: var(--color-tgl05-acc);
color: #fff;
}Setup — do this once per project
Skip this if your project already runs Tailwind v4.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>Compiles in the browser — perfect for a CodePen or a quick test, not for production.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@import "tailwindcss";
@theme {
--color-tgl05-acc: #9b8cf0;
--color-tgl05-acc: oklch(0.72 0.16 285);
--font-tgl05-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tgl05-spin: tgl05-spin 26s linear infinite;
}
/* @keyframes has no utility form — it is the one thing that must stay CSS. */
@keyframes tgl05-spin {
to {
transform: rotate(360deg);
}
}
.tgl-05 {
width: 100%;
min-height: 100svh;
}
.tgl-05 ::selection {
background: var(--color-tgl05-acc);
color: #fff;
}@import "tailwindcss";
@theme {
--color-tgl05-acc: #9b8cf0;
--color-tgl05-acc: oklch(0.72 0.16 285);
--font-tgl05-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tgl05-spin: tgl05-spin 26s linear infinite;
}
/* @keyframes has no utility form — it is the one thing that must stay CSS. */
@keyframes tgl05-spin {
to {
transform: rotate(360deg);
}
}
.tgl-05 {
width: 100%;
min-height: 100svh;
}
.tgl-05 ::selection {
background: var(--color-tgl05-acc);
color: #fff;
}<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- A
@theme--animate-*entry is the whole reason the ring and its satellites can share one animation from two different elements without repeating the timing anywhere. - The orbit transform is one arbitrary property rather than three composed utilities, because Tailwind's
rotate-*andtranslate-*would fight over transform order — the composed form would place the satellites wrong. - UI change from the CSS original: alt text is now descriptive per satellite instead of "Orbit photo 3", and each satellite is its own named group (
group/sat) so the thumbnail zoom no longer leaks from the ring's group.
Here's a working CSS demo 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: Tailwind Radial Orbit Image Gallery with Counter-Rotating Thumbnails
Source: https://codefronts.com/snippets/tailwind-css-image-gallery/tailwind-radial-orbit-image-gallery-counter-rotation/
Circular thumbnails spaced evenly around a central hero, revolving slowly like planets and pausing the moment you reach for one. The port's interesting move is that the ring animation becomes a real animate-* utility through @theme, while the per-satellite angle stays inline data.
## HTML
```html
<section class="tgl-05 w-full min-h-svh grid place-items-center px-5 py-10 font-tgl05-sans bg-[radial-gradient(120%_120%_at_50%_40%,oklch(0.26_0.06_290),oklch(0.09_0.02_280))] [--r:170px] max-[560px]:[--r:120px]" aria-label="Radial orbit gallery">
<div class="group relative grid place-items-center w-[calc(var(--r)*2+90px)] h-[calc(var(--r)*2+90px)]">
<div class="w-[120px] h-[120px] rounded-full overflow-hidden z-[2] shadow-[0_0_40px_-6px_var(--color-tgl05-acc),0_0_0_4px_rgba(255,255,255,0.1)]">
<img src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?q=80&w=300&h=300&auto=format&fit=crop" alt="Spiral galaxy, featured image" loading="lazy" decoding="async" class="w-full h-full object-cover">
</div>
<div class="absolute inset-0 animate-tgl05-spin group-hover:[animation-play-state:paused] group-focus-within:[animation-play-state:paused] motion-reduce:animate-none">
<a href="#" style="--a:0deg" class="group/sat absolute top-1/2 left-1/2 w-[66px] h-[66px] -m-[33px] rounded-full overflow-hidden no-underline animate-tgl05-spin [animation-direction:reverse] [transform:rotate(var(--a))_translate(var(--r))_rotate(calc(-1*var(--a)))] shadow-[0_0_0_3px_rgba(255,255,255,0.12),0_8px_20px_-8px_rgba(0,0,0,0.6)] transition-shadow duration-300 hover:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:outline-3 focus-visible:outline-tgl05-acc focus-visible:outline-offset-[3px] motion-reduce:animate-none max-[560px]:w-[54px] max-[560px]:h-[54px] max-[560px]:-m-[27px]">
<img src="https://images.unsplash.com/photo-1502672260266-1c1ef2d93688?q=80&w=200&h=200&auto=format&fit=crop" alt="Planet surface" loading="lazy" decoding="async" class="w-full h-full object-cover transition-transform duration-300 group-hover/sat:scale-[1.12] motion-reduce:transition-none">
</a>
<a href="#" style="--a:45deg" class="group/sat absolute top-1/2 left-1/2 w-[66px] h-[66px] -m-[33px] rounded-full overflow-hidden no-underline animate-tgl05-spin [animation-direction:reverse] [transform:rotate(var(--a))_translate(var(--r))_rotate(calc(-1*var(--a)))] shadow-[0_0_0_3px_rgba(255,255,255,0.12),0_8px_20px_-8px_rgba(0,0,0,0.6)] transition-shadow duration-300 hover:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:outline-3 focus-visible:outline-tgl05-acc focus-visible:outline-offset-[3px] motion-reduce:animate-none max-[560px]:w-[54px] max-[560px]:h-[54px] max-[560px]:-m-[27px]">
<img src="https://images.unsplash.com/photo-1620121692029-d088224ddc74?q=80&w=200&h=200&auto=format&fit=crop" alt="Full moon" loading="lazy" decoding="async" class="w-full h-full object-cover transition-transform duration-300 group-hover/sat:scale-[1.12] motion-reduce:transition-none">
</a>
<a href="#" style="--a:90deg" class="group/sat absolute top-1/2 left-1/2 w-[66px] h-[66px] -m-[33px] rounded-full overflow-hidden no-underline animate-tgl05-spin [animation-direction:reverse] [transform:rotate(var(--a))_translate(var(--r))_rotate(calc(-1*var(--a)))] shadow-[0_0_0_3px_rgba(255,255,255,0.12),0_8px_20px_-8px_rgba(0,0,0,0.6)] transition-shadow duration-300 hover:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:outline-3 focus-visible:outline-tgl05-acc focus-visible:outline-offset-[3px] motion-reduce:animate-none max-[560px]:w-[54px] max-[560px]:h-[54px] max-[560px]:-m-[27px]">
<img src="https://images.unsplash.com/photo-1524995997946-a1c2e315a42f?q=80&w=200&h=200&auto=format&fit=crop" alt="Bright star field" loading="lazy" decoding="async" class="w-full h-full object-cover transition-transform duration-300 group-hover/sat:scale-[1.12] motion-reduce:transition-none">
</a>
<a href="#" style="--a:135deg" class="group/sat absolute top-1/2 left-1/2 w-[66px] h-[66px] -m-[33px] rounded-full overflow-hidden no-underline animate-tgl05-spin [animation-direction:reverse] [transform:rotate(var(--a))_translate(var(--r))_rotate(calc(-1*var(--a)))] shadow-[0_0_0_3px_rgba(255,255,255,0.12),0_8px_20px_-8px_rgba(0,0,0,0.6)] transition-shadow duration-300 hover:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:outline-3 focus-visible:outline-tgl05-acc focus-visible:outline-offset-[3px] motion-reduce:animate-none max-[560px]:w-[54px] max-[560px]:h-[54px] max-[560px]:-m-[27px]">
<img src="https://images.unsplash.com/photo-1616486338812-3dadae4b4ace?q=80&w=200&h=200&auto=format&fit=crop" alt="Coloured nebula" loading="lazy" decoding="async" class="w-full h-full object-cover transition-transform duration-300 group-hover/sat:scale-[1.12] motion-reduce:transition-none">
</a>
<a href="#" style="--a:180deg" class="group/sat absolute top-1/2 left-1/2 w-[66px] h-[66px] -m-[33px] rounded-full overflow-hidden no-underline animate-tgl05-spin [animation-direction:reverse] [transform:rotate(var(--a))_translate(var(--r))_rotate(calc(-1*var(--a)))] shadow-[0_0_0_3px_rgba(255,255,255,0.12),0_8px_20px_-8px_rgba(0,0,0,0.6)] transition-shadow duration-300 hover:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:outline-3 focus-visible:outline-tgl05-acc focus-visible:outline-offset-[3px] motion-reduce:animate-none max-[560px]:w-[54px] max-[560px]:h-[54px] max-[560px]:-m-[27px]">
<img src="https://images.unsplash.com/photo-1517180102446-f3ece451e9d8?q=80&w=200&h=200&auto=format&fit=crop" alt="Comet trail" loading="lazy" decoding="async" class="w-full h-full object-cover transition-transform duration-300 group-hover/sat:scale-[1.12] motion-reduce:transition-none">
</a>
<a href="#" style="--a:225deg" class="group/sat absolute top-1/2 left-1/2 w-[66px] h-[66px] -m-[33px] rounded-full overflow-hidden no-underline animate-tgl05-spin [animation-direction:reverse] [transform:rotate(var(--a))_translate(var(--r))_rotate(calc(-1*var(--a)))] shadow-[0_0_0_3px_rgba(255,255,255,0.12),0_8px_20px_-8px_rgba(0,0,0,0.6)] transition-shadow duration-300 hover:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:outline-3 focus-visible:outline-tgl05-acc focus-visible:outline-offset-[3px] motion-reduce:animate-none max-[560px]:w-[54px] max-[560px]:h-[54px] max-[560px]:-m-[27px]">
<img src="https://images.unsplash.com/photo-1508700115892-45ecd05ae2ad?q=80&w=200&h=200&auto=format&fit=crop" alt="Night sky" loading="lazy" decoding="async" class="w-full h-full object-cover transition-transform duration-300 group-hover/sat:scale-[1.12] motion-reduce:transition-none">
</a>
<a href="#" style="--a:270deg" class="group/sat absolute top-1/2 left-1/2 w-[66px] h-[66px] -m-[33px] rounded-full overflow-hidden no-underline animate-tgl05-spin [animation-direction:reverse] [transform:rotate(var(--a))_translate(var(--r))_rotate(calc(-1*var(--a)))] shadow-[0_0_0_3px_rgba(255,255,255,0.12),0_8px_20px_-8px_rgba(0,0,0,0.6)] transition-shadow duration-300 hover:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:outline-3 focus-visible:outline-tgl05-acc focus-visible:outline-offset-[3px] motion-reduce:animate-none max-[560px]:w-[54px] max-[560px]:h-[54px] max-[560px]:-m-[27px]">
<img src="https://images.unsplash.com/photo-1590490360182-c33d57733427?q=80&w=200&h=200&auto=format&fit=crop" alt="Observatory telescope" loading="lazy" decoding="async" class="w-full h-full object-cover transition-transform duration-300 group-hover/sat:scale-[1.12] motion-reduce:transition-none">
</a>
<a href="#" style="--a:315deg" class="group/sat absolute top-1/2 left-1/2 w-[66px] h-[66px] -m-[33px] rounded-full overflow-hidden no-underline animate-tgl05-spin [animation-direction:reverse] [transform:rotate(var(--a))_translate(var(--r))_rotate(calc(-1*var(--a)))] shadow-[0_0_0_3px_rgba(255,255,255,0.12),0_8px_20px_-8px_rgba(0,0,0,0.6)] transition-shadow duration-300 hover:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:outline-3 focus-visible:outline-tgl05-acc focus-visible:outline-offset-[3px] motion-reduce:animate-none max-[560px]:w-[54px] max-[560px]:h-[54px] max-[560px]:-m-[27px]">
<img src="https://images.unsplash.com/photo-1444723121867-7a241cacace9?q=80&w=200&h=200&auto=format&fit=crop" alt="Star chart detail" loading="lazy" decoding="async" class="w-full h-full object-cover transition-transform duration-300 group-hover/sat:scale-[1.12] motion-reduce:transition-none">
</a>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tgl05-acc: #9b8cf0;
--color-tgl05-acc: oklch(0.72 0.16 285);
--font-tgl05-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tgl05-spin: tgl05-spin 26s linear infinite;
}
/* @keyframes has no utility form — it is the one thing that must stay CSS. */
@keyframes tgl05-spin {
to {
transform: rotate(360deg);
}
}
.tgl-05 {
width: 100%;
min-height: 100svh;
}
.tgl-05 ::selection {
background: var(--color-tgl05-acc);
color: #fff;
}
```Here's a working CSS demo 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: Tailwind Radial Orbit Image Gallery with Counter-Rotating Thumbnails
Source: https://codefronts.com/snippets/tailwind-css-image-gallery/tailwind-radial-orbit-image-gallery-counter-rotation/
Circular thumbnails spaced evenly around a central hero, revolving slowly like planets and pausing the moment you reach for one. The port's interesting move is that the ring animation becomes a real animate-* utility through @theme, while the per-satellite angle stays inline data.
## HTML
```html
<section class="tgl-05 w-full min-h-svh grid place-items-center px-5 py-10 font-tgl05-sans bg-[radial-gradient(120%_120%_at_50%_40%,oklch(0.26_0.06_290),oklch(0.09_0.02_280))] [--r:170px] max-[560px]:[--r:120px]" aria-label="Radial orbit gallery">
<div class="group relative grid place-items-center w-[calc(var(--r)*2+90px)] h-[calc(var(--r)*2+90px)]">
<div class="w-[120px] h-[120px] rounded-full overflow-hidden z-[2] shadow-[0_0_40px_-6px_var(--color-tgl05-acc),0_0_0_4px_rgba(255,255,255,0.1)]">
<img src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?q=80&w=300&h=300&auto=format&fit=crop" alt="Spiral galaxy, featured image" loading="lazy" decoding="async" class="w-full h-full object-cover">
</div>
<div class="absolute inset-0 animate-tgl05-spin group-hover:[animation-play-state:paused] group-focus-within:[animation-play-state:paused] motion-reduce:animate-none">
<a href="#" style="--a:0deg" class="group/sat absolute top-1/2 left-1/2 w-[66px] h-[66px] -m-[33px] rounded-full overflow-hidden no-underline animate-tgl05-spin [animation-direction:reverse] [transform:rotate(var(--a))_translate(var(--r))_rotate(calc(-1*var(--a)))] shadow-[0_0_0_3px_rgba(255,255,255,0.12),0_8px_20px_-8px_rgba(0,0,0,0.6)] transition-shadow duration-300 hover:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:outline-3 focus-visible:outline-tgl05-acc focus-visible:outline-offset-[3px] motion-reduce:animate-none max-[560px]:w-[54px] max-[560px]:h-[54px] max-[560px]:-m-[27px]">
<img src="https://images.unsplash.com/photo-1502672260266-1c1ef2d93688?q=80&w=200&h=200&auto=format&fit=crop" alt="Planet surface" loading="lazy" decoding="async" class="w-full h-full object-cover transition-transform duration-300 group-hover/sat:scale-[1.12] motion-reduce:transition-none">
</a>
<a href="#" style="--a:45deg" class="group/sat absolute top-1/2 left-1/2 w-[66px] h-[66px] -m-[33px] rounded-full overflow-hidden no-underline animate-tgl05-spin [animation-direction:reverse] [transform:rotate(var(--a))_translate(var(--r))_rotate(calc(-1*var(--a)))] shadow-[0_0_0_3px_rgba(255,255,255,0.12),0_8px_20px_-8px_rgba(0,0,0,0.6)] transition-shadow duration-300 hover:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:outline-3 focus-visible:outline-tgl05-acc focus-visible:outline-offset-[3px] motion-reduce:animate-none max-[560px]:w-[54px] max-[560px]:h-[54px] max-[560px]:-m-[27px]">
<img src="https://images.unsplash.com/photo-1620121692029-d088224ddc74?q=80&w=200&h=200&auto=format&fit=crop" alt="Full moon" loading="lazy" decoding="async" class="w-full h-full object-cover transition-transform duration-300 group-hover/sat:scale-[1.12] motion-reduce:transition-none">
</a>
<a href="#" style="--a:90deg" class="group/sat absolute top-1/2 left-1/2 w-[66px] h-[66px] -m-[33px] rounded-full overflow-hidden no-underline animate-tgl05-spin [animation-direction:reverse] [transform:rotate(var(--a))_translate(var(--r))_rotate(calc(-1*var(--a)))] shadow-[0_0_0_3px_rgba(255,255,255,0.12),0_8px_20px_-8px_rgba(0,0,0,0.6)] transition-shadow duration-300 hover:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:outline-3 focus-visible:outline-tgl05-acc focus-visible:outline-offset-[3px] motion-reduce:animate-none max-[560px]:w-[54px] max-[560px]:h-[54px] max-[560px]:-m-[27px]">
<img src="https://images.unsplash.com/photo-1524995997946-a1c2e315a42f?q=80&w=200&h=200&auto=format&fit=crop" alt="Bright star field" loading="lazy" decoding="async" class="w-full h-full object-cover transition-transform duration-300 group-hover/sat:scale-[1.12] motion-reduce:transition-none">
</a>
<a href="#" style="--a:135deg" class="group/sat absolute top-1/2 left-1/2 w-[66px] h-[66px] -m-[33px] rounded-full overflow-hidden no-underline animate-tgl05-spin [animation-direction:reverse] [transform:rotate(var(--a))_translate(var(--r))_rotate(calc(-1*var(--a)))] shadow-[0_0_0_3px_rgba(255,255,255,0.12),0_8px_20px_-8px_rgba(0,0,0,0.6)] transition-shadow duration-300 hover:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:outline-3 focus-visible:outline-tgl05-acc focus-visible:outline-offset-[3px] motion-reduce:animate-none max-[560px]:w-[54px] max-[560px]:h-[54px] max-[560px]:-m-[27px]">
<img src="https://images.unsplash.com/photo-1616486338812-3dadae4b4ace?q=80&w=200&h=200&auto=format&fit=crop" alt="Coloured nebula" loading="lazy" decoding="async" class="w-full h-full object-cover transition-transform duration-300 group-hover/sat:scale-[1.12] motion-reduce:transition-none">
</a>
<a href="#" style="--a:180deg" class="group/sat absolute top-1/2 left-1/2 w-[66px] h-[66px] -m-[33px] rounded-full overflow-hidden no-underline animate-tgl05-spin [animation-direction:reverse] [transform:rotate(var(--a))_translate(var(--r))_rotate(calc(-1*var(--a)))] shadow-[0_0_0_3px_rgba(255,255,255,0.12),0_8px_20px_-8px_rgba(0,0,0,0.6)] transition-shadow duration-300 hover:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:outline-3 focus-visible:outline-tgl05-acc focus-visible:outline-offset-[3px] motion-reduce:animate-none max-[560px]:w-[54px] max-[560px]:h-[54px] max-[560px]:-m-[27px]">
<img src="https://images.unsplash.com/photo-1517180102446-f3ece451e9d8?q=80&w=200&h=200&auto=format&fit=crop" alt="Comet trail" loading="lazy" decoding="async" class="w-full h-full object-cover transition-transform duration-300 group-hover/sat:scale-[1.12] motion-reduce:transition-none">
</a>
<a href="#" style="--a:225deg" class="group/sat absolute top-1/2 left-1/2 w-[66px] h-[66px] -m-[33px] rounded-full overflow-hidden no-underline animate-tgl05-spin [animation-direction:reverse] [transform:rotate(var(--a))_translate(var(--r))_rotate(calc(-1*var(--a)))] shadow-[0_0_0_3px_rgba(255,255,255,0.12),0_8px_20px_-8px_rgba(0,0,0,0.6)] transition-shadow duration-300 hover:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:outline-3 focus-visible:outline-tgl05-acc focus-visible:outline-offset-[3px] motion-reduce:animate-none max-[560px]:w-[54px] max-[560px]:h-[54px] max-[560px]:-m-[27px]">
<img src="https://images.unsplash.com/photo-1508700115892-45ecd05ae2ad?q=80&w=200&h=200&auto=format&fit=crop" alt="Night sky" loading="lazy" decoding="async" class="w-full h-full object-cover transition-transform duration-300 group-hover/sat:scale-[1.12] motion-reduce:transition-none">
</a>
<a href="#" style="--a:270deg" class="group/sat absolute top-1/2 left-1/2 w-[66px] h-[66px] -m-[33px] rounded-full overflow-hidden no-underline animate-tgl05-spin [animation-direction:reverse] [transform:rotate(var(--a))_translate(var(--r))_rotate(calc(-1*var(--a)))] shadow-[0_0_0_3px_rgba(255,255,255,0.12),0_8px_20px_-8px_rgba(0,0,0,0.6)] transition-shadow duration-300 hover:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:outline-3 focus-visible:outline-tgl05-acc focus-visible:outline-offset-[3px] motion-reduce:animate-none max-[560px]:w-[54px] max-[560px]:h-[54px] max-[560px]:-m-[27px]">
<img src="https://images.unsplash.com/photo-1590490360182-c33d57733427?q=80&w=200&h=200&auto=format&fit=crop" alt="Observatory telescope" loading="lazy" decoding="async" class="w-full h-full object-cover transition-transform duration-300 group-hover/sat:scale-[1.12] motion-reduce:transition-none">
</a>
<a href="#" style="--a:315deg" class="group/sat absolute top-1/2 left-1/2 w-[66px] h-[66px] -m-[33px] rounded-full overflow-hidden no-underline animate-tgl05-spin [animation-direction:reverse] [transform:rotate(var(--a))_translate(var(--r))_rotate(calc(-1*var(--a)))] shadow-[0_0_0_3px_rgba(255,255,255,0.12),0_8px_20px_-8px_rgba(0,0,0,0.6)] transition-shadow duration-300 hover:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:shadow-[0_0_0_3px_var(--color-tgl05-acc)] focus-visible:outline-3 focus-visible:outline-tgl05-acc focus-visible:outline-offset-[3px] motion-reduce:animate-none max-[560px]:w-[54px] max-[560px]:h-[54px] max-[560px]:-m-[27px]">
<img src="https://images.unsplash.com/photo-1444723121867-7a241cacace9?q=80&w=200&h=200&auto=format&fit=crop" alt="Star chart detail" loading="lazy" decoding="async" class="w-full h-full object-cover transition-transform duration-300 group-hover/sat:scale-[1.12] motion-reduce:transition-none">
</a>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tgl05-acc: #9b8cf0;
--color-tgl05-acc: oklch(0.72 0.16 285);
--font-tgl05-sans: 'Space Grotesk', system-ui, sans-serif;
--animate-tgl05-spin: tgl05-spin 26s linear infinite;
}
/* @keyframes has no utility form — it is the one thing that must stay CSS. */
@keyframes tgl05-spin {
to {
transform: rotate(360deg);
}
}
.tgl-05 {
width: 100%;
min-height: 100svh;
}
.tgl-05 ::selection {
background: var(--color-tgl05-acc);
color: #fff;
}
```How this works
Two animations, one keyframe. The ring runs animate-tgl05-spin; each satellite runs the same animation with [animation-direction:reverse], which cancels the ring's rotation so the photographs never tumble end over end. One @theme entry serves both.
The angle is data. style="--a:45deg" per satellite, read by the shared transform utility — 360 ÷ count, and adding a ninth satellite is one element plus one number.
Pause is a state, not a script. group-hover: and group-focus-within: on the system element freeze both animations, so a moving target holds still as soon as you aim at it.
Make it yours
- Orbit size:
[--r:170px]on the root; the stage sizes itself from it. - Speed or direction: edit the
@themeanimation entry once — both ring and satellites follow. - Swap the centre for a logo or a call-to-action; it is an ordinary flex child at
z-[2].
Gotchas — read before shipping
- Every satellite must counter-rotate AND reverse its animation; miss either and the images spin.
- Keep the pause on
:focus-withinas well as:hover— tabbing onto a moving link is unusable otherwise. motion-reduce:animate-noneleaves the ring in its resting arrangement, which is the correct static fallback; do not just slow it down.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Transforms and keyframes are universal; the floor is Tailwind v4's generated sheet.