25 CSS Spinners 19 / 25
Breathing Circle Loader
A calm azure orb slowly inhales and exhales with a scale pulse while three expanding rings propagate outward in a continuous ripple, creating a meditative loading state.
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-19">
<div class="sp-19__wrap">
<div class="sp-19__ring"></div>
<div class="sp-19__ring"></div>
<div class="sp-19__ring"></div>
<div class="sp-19__core"></div>
</div>
<div class="sp-19__label">Loading</div>
</div> <div class="sp-19">
<div class="sp-19__wrap">
<div class="sp-19__ring"></div>
<div class="sp-19__ring"></div>
<div class="sp-19__ring"></div>
<div class="sp-19__core"></div>
</div>
<div class="sp-19__label">Loading</div>
</div>.sp-19,.sp-19 *,.sp-19 *::before,.sp-19 *::after{box-sizing:border-box;margin:0;padding:0}
.sp-19{
--bg:#03080e;
--c:#4fc3f7;
--glow:rgba(79,195,247,0.3);
display:flex;
flex-direction:column;
align-items:center;
justify-content:center;
gap:20px;
min-height:100vh;
background:var(--bg);
font-family:system-ui,sans-serif;
}
.sp-19__wrap{
position:relative;
width:90px;
height:90px;
}
.sp-19__ring{
position:absolute;
border-radius:50%;
border:2px solid var(--c);
opacity:0;
animation:sp-19-breathe-ring 4s ease-in-out infinite;
}
.sp-19__ring:nth-child(1){inset:0}
.sp-19__ring:nth-child(2){inset:0;animation-delay:1.3s}
.sp-19__ring:nth-child(3){inset:0;animation-delay:2.6s}
.sp-19__core{
position:absolute;
inset:20px;
border-radius:50%;
background:radial-gradient(circle,var(--c),rgba(79,195,247,0.3));
animation:sp-19-breathe-core 4s ease-in-out infinite;
}
.sp-19__label{
color:rgba(79,195,247,0.7);
font-size:11px;
letter-spacing:3px;
text-transform:uppercase;
animation:sp-19-text 4s ease-in-out infinite;
}
@keyframes sp-19-breathe-ring{
0%{transform:scale(0.4);opacity:0.8}
100%{transform:scale(1.8);opacity:0}
}
@keyframes sp-19-breathe-core{
0%,100%{transform:scale(0.7);box-shadow:0 0 10px var(--glow)}
50%{transform:scale(1.15);box-shadow:0 0 30px var(--glow),0 0 60px var(--glow)}
}
@keyframes sp-19-text{
0%,100%{opacity:0.3}
50%{opacity:1}
}
@media (prefers-reduced-motion: reduce){
.sp-19__ring,.sp-19__core,.sp-19__label{animation:none;opacity:1}
} .sp-19,.sp-19 *,.sp-19 *::before,.sp-19 *::after{box-sizing:border-box;margin:0;padding:0}
.sp-19{
--bg:#03080e;
--c:#4fc3f7;
--glow:rgba(79,195,247,0.3);
display:flex;
flex-direction:column;
align-items:center;
justify-content:center;
gap:20px;
min-height:100vh;
background:var(--bg);
font-family:system-ui,sans-serif;
}
.sp-19__wrap{
position:relative;
width:90px;
height:90px;
}
.sp-19__ring{
position:absolute;
border-radius:50%;
border:2px solid var(--c);
opacity:0;
animation:sp-19-breathe-ring 4s ease-in-out infinite;
}
.sp-19__ring:nth-child(1){inset:0}
.sp-19__ring:nth-child(2){inset:0;animation-delay:1.3s}
.sp-19__ring:nth-child(3){inset:0;animation-delay:2.6s}
.sp-19__core{
position:absolute;
inset:20px;
border-radius:50%;
background:radial-gradient(circle,var(--c),rgba(79,195,247,0.3));
animation:sp-19-breathe-core 4s ease-in-out infinite;
}
.sp-19__label{
color:rgba(79,195,247,0.7);
font-size:11px;
letter-spacing:3px;
text-transform:uppercase;
animation:sp-19-text 4s ease-in-out infinite;
}
@keyframes sp-19-breathe-ring{
0%{transform:scale(0.4);opacity:0.8}
100%{transform:scale(1.8);opacity:0}
}
@keyframes sp-19-breathe-core{
0%,100%{transform:scale(0.7);box-shadow:0 0 10px var(--glow)}
50%{transform:scale(1.15);box-shadow:0 0 30px var(--glow),0 0 60px var(--glow)}
}
@keyframes sp-19-text{
0%,100%{opacity:0.3}
50%{opacity:1}
}
@media (prefers-reduced-motion: reduce){
.sp-19__ring,.sp-19__core,.sp-19__label{animation:none;opacity:1}
}How this works
The core element runs sp-19-breathe-core: a 4-second ease-in-out cycle from scale(0.7) to scale(1.15) and back, with a simultaneously-animated box-shadow glow that expands from a tight to a diffuse halo — mimicking the glow intensifying on the exhale. The 4-second duration is chosen to match a typical adult breathing cycle, making the animation feel physiologically grounded.
Three rings run sp-19-breathe-ring each starting at scale(0.4) with 0.8 opacity and expanding to scale(1.8) at zero opacity. Offset by 1.3s each (one-third of the 4s period), they create a constant outward propagation that mirrors the core's exhale rhythm.
Customize
- Edit
--cto change the orb colour — a warm amber creates a candle-flame breathing loader; deep green suits wellness or eco-themed apps. - Change the duration from
4sto6sfor a slower, deeper breathing pace, or2sfor a quicker cadence suitable as an active spinner. - Increase ring scale from
1.8to2.5for wider propagation — useful over large modal overlays. - Add a text label that fades between "Loading…" and "Please wait…" using a separate opacity keyframe on a sibling element.
- Reduce to one ring by removing two
.sp-19__ringelements for a minimal meditative pulse without the ripple effect.
Watch out for
- The 4s duration with three rings offset by 1.3s leaves a small phase error (3 × 1.3 = 3.9s, not 4s) — rings gradually drift over many cycles; use
1.333sdelay to keep them in phase. box-shadowchanges in keyframes are not composited and trigger a repaint on each frame — switch to a pseudo-element with a scale/opacity animation for a compositor-only approach.- The
Loadingtext label uses letter-spacing animation for effect — this is not compositor-friendly; if FPS is critical, remove the label animation.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 60+ | 12+ | 60+ | 60+ |
Widely supported; box-shadow animation in keyframes triggers repaint but is acceptable for a single-instance spinner.