CSS Loading Animations: Spinners, Skeletons & Progress Bars Done Right
A loading state is your UI's patience-keeper. Learn how to build spinners, skeleton screens, progress bars, and indicator animations that feel smooth and professional — pure CSS.
UIXplor Team
February 24, 2026 · 7 min read
01Loading States Are UX Promises
Every loading animation is a promise: 'Something is happening. Please wait.' Break that promise — or make the wait feel longer than it is — and users leave.
Effective loading states have three qualities: 1. Visibility — the animation must be clearly visible at a glance 2. Continuity — it should loop smoothly, with no jarring resets 3. Appropriateness — a playful bouncing dot is great for chat; a clean ring spinner for data tables
Choose your loader to match the feel of the content it's waiting for.
02The Classic Ring Spinner
The ring spinner is the universal symbol of loading. Its simplicity is its strength:
.ring-spinner {
width: 40px;
height: 40px;
border-radius: 50%;
border: 3px solid rgba(255, 255, 255, 0.1); /* faint track */
border-top-color: #B8FB3C; /* active arc */
animation: spin 0.75s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }The key: `linear` timing makes rotation feel mechanical and stable. `ease` would cause an accelerate-decelerate that makes it look wobbly.
03Skeleton Screens: The Modern Loading Pattern
Skeleton screens are placeholder layouts that mirror the shape of the incoming content. They feel faster than spinners because users can see *where* content is about to appear.
.skel-line {
height: 14px;
border-radius: 6px;
background: rgba(255, 255, 255, 0.07);
position: relative;
overflow: hidden;
}
.skel-line::after {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(
90deg,
transparent,
rgba(255, 255, 255, 0.08),
transparent
);
animation: shimmer 1.4s infinite;
}
@keyframes shimmer {
from { transform: translateX(-100%); }
to { transform: translateX(100%); }
}The shimmer moves *left to right* — matching the direction we read — which makes the wait feel like progress.
04Gradient Conic Spinner
For a more modern look, conic gradients create seamless gradient arcs:
.gradient-ring {
width: 44px; height: 44px;
border-radius: 50%;
background: conic-gradient(
#B8FB3C 0%,
#a855f7 40%,
transparent 40%
);
animation: spin 1s linear infinite;
/* Mask the center to create ring effect */
mask: radial-gradient(
farthest-side,
transparent calc(100% - 4px),
#fff calc(100% - 4px)
);
-webkit-mask: radial-gradient(
farthest-side,
transparent calc(100% - 4px),
#fff calc(100% - 4px)
);
}The `mask` property carves out the center of the circle, revealing only the ring. No borders needed.
05Animated Progress Bars
When you know the percentage of completion, use a progress bar. When you don't (indeterminate), use an animated stripe:
/* Indeterminate progress bar */
.progress-bar { height: 3px; background: rgba(255,255,255,0.1); overflow: hidden; border-radius: 2px; }
.progress-fill {
height: 100%;
background: linear-gradient(to right, #B8FB3C, #06b6d4);
animation: progress-slide 1.8s ease-in-out infinite;
}
@keyframes progress-slide {
0% { transform: translateX(-100%) scaleX(0.3); }
50% { transform: translateX(0%) scaleX(0.7); }
100% { transform: translateX(100%) scaleX(0.3); }
}For a *determinate* bar, just set `width` on `.progress-fill` with a `transition: width 0.3s ease`.
06Performance Considerations
CSS animations are fast, but there are rules:
1. Only animate `transform` and `opacity` — these are GPU-accelerated and don't trigger layout recalculation 2. Avoid animating `width`, `height`, `top`, `left` — these cause expensive reflows 3. Use `will-change: transform` — promotes the element to its own composite layer. Use sparingly. 4. Respect `prefers-reduced-motion`:
@media (prefers-reduced-motion: reduce) {
.ring-spinner,
.skel-line::after {
animation: none;
}
}Animation is an enhancement, not a requirement. Make sure your loading state works without it.
Related UI Components
Related Articles