25 CSS Spinners 08 / 25
Morph Square-to-Circle Spinner
A hollow square morphs fluidly through four asymmetric border-radius stages into a perfect circle and back, while simultaneously rotating — a classic CSS shape-morphing technique.
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-08">
<div class="sp-08__outer">
<div class="sp-08__track"></div>
<div class="sp-08__shape"></div>
</div>
</div> <div class="sp-08">
<div class="sp-08__outer">
<div class="sp-08__track"></div>
<div class="sp-08__shape"></div>
</div>
</div>.sp-08,.sp-08 *,.sp-08 *::before,.sp-08 *::after{box-sizing:border-box;margin:0;padding:0}
.sp-08{
--bg:#0e0e12;
--c:#ff9100;
--glow:rgba(255,145,0,0.4);
display:flex;
align-items:center;
justify-content:center;
min-height:100vh;
background:var(--bg);
}
.sp-08__shape{
width:60px;
height:60px;
background:transparent;
border:4px solid var(--c);
filter:drop-shadow(0 0 8px var(--glow));
animation:sp-08-morph 2s ease-in-out infinite, sp-08-spin 2s linear infinite;
}
@keyframes sp-08-morph{
0%,100%{border-radius:4px;transform:rotate(0deg) scale(1)}
25%{border-radius:50% 4px 50% 4px}
50%{border-radius:50%;transform:rotate(180deg) scale(1.15)}
75%{border-radius:4px 50% 4px 50%}
}
@keyframes sp-08-spin{
0%{transform:rotate(0deg)}
100%{transform:rotate(360deg)}
}
.sp-08__outer{
position:relative;
width:80px;
height:80px;
display:flex;
align-items:center;
justify-content:center;
}
.sp-08__track{
position:absolute;
inset:0;
border-radius:50%;
border:1px solid rgba(255,145,0,0.1);
}
@media (prefers-reduced-motion: reduce){
.sp-08__shape{animation:none;border-radius:8px}
} .sp-08,.sp-08 *,.sp-08 *::before,.sp-08 *::after{box-sizing:border-box;margin:0;padding:0}
.sp-08{
--bg:#0e0e12;
--c:#ff9100;
--glow:rgba(255,145,0,0.4);
display:flex;
align-items:center;
justify-content:center;
min-height:100vh;
background:var(--bg);
}
.sp-08__shape{
width:60px;
height:60px;
background:transparent;
border:4px solid var(--c);
filter:drop-shadow(0 0 8px var(--glow));
animation:sp-08-morph 2s ease-in-out infinite, sp-08-spin 2s linear infinite;
}
@keyframes sp-08-morph{
0%,100%{border-radius:4px;transform:rotate(0deg) scale(1)}
25%{border-radius:50% 4px 50% 4px}
50%{border-radius:50%;transform:rotate(180deg) scale(1.15)}
75%{border-radius:4px 50% 4px 50%}
}
@keyframes sp-08-spin{
0%{transform:rotate(0deg)}
100%{transform:rotate(360deg)}
}
.sp-08__outer{
position:relative;
width:80px;
height:80px;
display:flex;
align-items:center;
justify-content:center;
}
.sp-08__track{
position:absolute;
inset:0;
border-radius:50%;
border:1px solid rgba(255,145,0,0.1);
}
@media (prefers-reduced-motion: reduce){
.sp-08__shape{animation:none;border-radius:8px}
}How this works
A single element uses two simultaneous animations: sp-08-morph drives the border-radius through four stages (sharp square → two-corner pill → circle → opposing two-corner pill → back), while sp-08-spin rotates the element 360° over the same period. Because both share the same 2s duration, the morph stages align with compass-point rotations, making the shape appear to tumble.
The element is otherwise a transparent rectangle with a coloured border and a filter:drop-shadow glow, so the shape outline is the primary visual. No child elements are needed — all motion is driven by the single div's transform and border-radius keyframes.
Customize
- Change the shape at each morph stage by editing the four
border-radiuskeyframe values — try a star-like40% 60% 60% 40% / 60% 30% 70% 40%for organic forms. - Make the shape solid by replacing
borderwithbackground:var(--c)and removing the border declarations. - Add a second counter-rotating copy at 50% opacity for a yin-yang-like interference pattern.
- Set
animation-timing-function:steps(4,end)to snap between shapes for a retro, frame-by-frame feel. - Reduce the element size to
40pxfor a compact inline spinner that fits in button labels.
Watch out for
- Two simultaneous animation references on a single element in the CSS shorthand (
animation: a 2s ..., b 2s ...) must share timing values carefully — any mismatch causes the morph and spin to drift out of sync. filter:drop-shadowrepaints on every frame during a morph since the painted shape changes —box-shadowis cheaper but only follows the border-box, not the morphed outline.border-radiusanimation is interpolated by the CSS engine which can look jerky at large step changes; keep adjacent keyframe values close (within 20–30%) for smooth transitions.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 60+ | 12+ | 60+ | 60+ |
Multi-value border-radius animation is well-supported across all modern engines.