25 CSS Spinners 02 / 25
Dual Counter-Rotate Ring Spinner
Two concentric rings spin in opposite directions — outer purple and inner pink — with a pulsing radial core, creating a hypnotic, gyroscopic loading indicator.
This is a full-page demo — interact inside the frame above, or open it in the playground for the full-screen experience.
The code
<div class="sp-02">
<div class="sp-02__wrap">
<div class="sp-02__outer-track"></div>
<div class="sp-02__outer"></div>
<div class="sp-02__inner-track"></div>
<div class="sp-02__inner"></div>
<div class="sp-02__core"></div>
</div>
</div> <div class="sp-02">
<div class="sp-02__wrap">
<div class="sp-02__outer-track"></div>
<div class="sp-02__outer"></div>
<div class="sp-02__inner-track"></div>
<div class="sp-02__inner"></div>
<div class="sp-02__core"></div>
</div>
</div>.sp-02,.sp-02 *,.sp-02 *::before,.sp-02 *::after{box-sizing:border-box;margin:0;padding:0}
.sp-02{
--bg:#0d0017;
--outer:#b44fff;
--inner:#ff4fa3;
--outer-track:rgba(180,79,255,0.15);
--inner-track:rgba(255,79,163,0.15);
display:flex;
align-items:center;
justify-content:center;
min-height:100vh;
background:var(--bg);
}
.sp-02__wrap{
position:relative;
width:80px;
height:80px;
}
.sp-02__outer-track{
position:absolute;
inset:0;
border-radius:50%;
border:4px solid var(--outer-track);
}
.sp-02__outer{
position:absolute;
inset:0;
border-radius:50%;
border:4px solid transparent;
border-top-color:var(--outer);
border-bottom-color:var(--outer);
animation:sp-02-cw 1.2s linear infinite;
filter:drop-shadow(0 0 6px var(--outer));
}
.sp-02__inner-track{
position:absolute;
inset:14px;
border-radius:50%;
border:3px solid var(--inner-track);
}
.sp-02__inner{
position:absolute;
inset:14px;
border-radius:50%;
border:3px solid transparent;
border-left-color:var(--inner);
border-right-color:var(--inner);
animation:sp-02-ccw 0.9s linear infinite;
filter:drop-shadow(0 0 6px var(--inner));
}
.sp-02__core{
position:absolute;
inset:32px;
border-radius:50%;
background:radial-gradient(circle,rgba(180,79,255,0.6),transparent);
animation:sp-02-pulse 1.2s ease-in-out infinite;
}
@keyframes sp-02-cw{
to{transform:rotate(360deg)}
}
@keyframes sp-02-ccw{
to{transform:rotate(-360deg)}
}
@keyframes sp-02-pulse{
0%,100%{opacity:0.4;transform:scale(0.8)}
50%{opacity:1;transform:scale(1.2)}
}
@media (prefers-reduced-motion: reduce){
.sp-02__outer,.sp-02__inner,.sp-02__core{animation:none}
} .sp-02,.sp-02 *,.sp-02 *::before,.sp-02 *::after{box-sizing:border-box;margin:0;padding:0}
.sp-02{
--bg:#0d0017;
--outer:#b44fff;
--inner:#ff4fa3;
--outer-track:rgba(180,79,255,0.15);
--inner-track:rgba(255,79,163,0.15);
display:flex;
align-items:center;
justify-content:center;
min-height:100vh;
background:var(--bg);
}
.sp-02__wrap{
position:relative;
width:80px;
height:80px;
}
.sp-02__outer-track{
position:absolute;
inset:0;
border-radius:50%;
border:4px solid var(--outer-track);
}
.sp-02__outer{
position:absolute;
inset:0;
border-radius:50%;
border:4px solid transparent;
border-top-color:var(--outer);
border-bottom-color:var(--outer);
animation:sp-02-cw 1.2s linear infinite;
filter:drop-shadow(0 0 6px var(--outer));
}
.sp-02__inner-track{
position:absolute;
inset:14px;
border-radius:50%;
border:3px solid var(--inner-track);
}
.sp-02__inner{
position:absolute;
inset:14px;
border-radius:50%;
border:3px solid transparent;
border-left-color:var(--inner);
border-right-color:var(--inner);
animation:sp-02-ccw 0.9s linear infinite;
filter:drop-shadow(0 0 6px var(--inner));
}
.sp-02__core{
position:absolute;
inset:32px;
border-radius:50%;
background:radial-gradient(circle,rgba(180,79,255,0.6),transparent);
animation:sp-02-pulse 1.2s ease-in-out infinite;
}
@keyframes sp-02-cw{
to{transform:rotate(360deg)}
}
@keyframes sp-02-ccw{
to{transform:rotate(-360deg)}
}
@keyframes sp-02-pulse{
0%,100%{opacity:0.4;transform:scale(0.8)}
50%{opacity:1;transform:scale(1.2)}
}
@media (prefers-reduced-motion: reduce){
.sp-02__outer,.sp-02__inner,.sp-02__core{animation:none}
}How this works
The outer ring is a full-width circle with border-top-color and border-bottom-color set and the sides transparent, animated with sp-02-cw (clockwise). The inner ring — inset by 14px — uses border-left-color and border-right-color and runs sp-02-ccw (counter-clockwise), creating a perpendicular axis visual at matching speed offset.
The centremost element animates opacity and scale via a radial-gradient background that pulses from 0.8 to 1.2 scale, adding depth. All three keyframes are independent so duration tweaks to any layer change the beat without affecting the others.
Customize
- Swap
--outerand--innercolours to any hue pair for brand theming. - Change the outer spin speed from
1.2sindependently of the inner0.9sto create different interference patterns. - Add a third outermost ring by duplicating the outer track/ring pair with
inset:-12pxfor a triple-layer gyroscope. - Replace
border-top/bottomwith a full 4-sideborderand useclip-path:inset(0 50% 0 0)for a half-arc look. - Increase the core element to
inset:20pxfor a larger glowing centre orb.
Watch out for
- Two simultaneous infinite rotation animations share the GPU raster layer — test on low-end Android where this can cause jank at 60 Hz.
- Counter-rotation with
animation-direction:reverseon a sibling (not child) element means transforms do not compound — they are independent rotations from the same centre. - The core pulsing uses
scaleinside a@keyframes, which triggers layout in IE 11 (not an issue in modern browsers but worth noting for legacy audit trails).
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 60+ | 12+ | 60+ | 60+ |
No cutting-edge features; fully supported in all modern browsers.