CSS Loading Animations — 10 Spinners, Bars & Skeletons with Code
Build 10 CSS loading animations from scratch: pulse dots, spinning ring, morphing shape, progress bar with shimmer, skeleton loader, equalizer bars, circle spinner, bouncing dots, orbit dots, and 3D square flip. Full copy-paste code.
// table of contents (21 sections)
Try them live — All 10 loading animations are available on CSSKit where you can customize parameters and copy the code. Source code on GitHub. See the full series: Text Part 1, Text Part 2, Hover Part 1, Hover Part 2.
Every web app needs loading states. Whether it’s a full-page spinner, an inline skeleton, or a progress indicator, the right loading animation sets user expectations and prevents frustration. In this post, we’ll build 10 CSS loading animations — all pure CSS, no JavaScript, no libraries.
45. Pulse Dots
Three dots pulsing in sequence — the classic “typing” indicator.
How It Works
Three identical <span> elements, each with a staggered animation-delay (0s, 0.2s, 0.4s). The animation pulses between scale(0.6) with opacity: 0.4 and scale(1) with opacity: 1. The 0.2s delay between each creates the sequential wave pattern. The 80% hold at the small state ensures each dot stays small while the next one pulses.
.pulse-dots {
--dot-color: #60a5fa;
--dot-size: 14px;
--dot-speed: 1.4s;
display: inline-flex;
gap: 8px;
align-items: center;
}
.pulse-dots span {
width: var(--dot-size);
height: var(--dot-size);
background: var(--dot-color);
border-radius: 50%;
animation: dot-pulse var(--dot-speed) ease-in-out infinite;
}
.pulse-dots span:nth-child(1) { animation-delay: 0s; }
.pulse-dots span:nth-child(2) { animation-delay: 0.2s; }
.pulse-dots span:nth-child(3) { animation-delay: 0.4s; }
@keyframes dot-pulse {
0%, 80%, 100% {
transform: scale(0.6);
opacity: 0.4;
}
40% {
transform: scale(1);
opacity: 1;
}
}
<div class="pulse-dots">
<span></span><span></span><span></span>
</div>
Tip: Add more <span> elements with incrementing delays for a longer pulse chain.
46. Spinning Ring
A spinning ring with a colored arc — the most common loading spinner.
How It Works
The trick is in the border shorthand. A uniform border is set at rgba(255,255,255,0.1) for the full circle, then border-top-color overrides just one edge with the accent color. When transform: rotate(360deg) animates, the colored edge spins around. The linear timing ensures constant speed — no easing.
.spinning-ring {
--ring-size: 48px;
--ring-thickness: 4px;
--ring-speed: 1s;
--ring-color: #f472b6;
width: var(--ring-size);
height: var(--ring-size);
border-radius: 50%;
border: var(--ring-thickness) solid rgba(255,255,255,0.1);
border-top-color: var(--ring-color);
animation: ring-spin var(--ring-speed) linear infinite;
}
@keyframes ring-spin {
to { transform: rotate(360deg); }
}
<div class="spinning-ring"></div>
Tip: Use border-left-color instead of border-top-color to start the arc from the left. Combine multiple border sides for a thicker arc.
47. Morphing Shape
A shape that continuously morphs between circle, square, and organic forms.
How It Works
Four keyframe stops each combine a different border-radius pattern with rotate() and scale(). At 25% it’s a square (border-radius: 10%), at 50% it’s an asymmetric blob (border-radius: 50% 10%), at 75% it mirrors the blob. The rotation (0 → 90 → 180 → 270 → 360) adds a tumbling motion, while the scale oscillation (1 → 0.9 → 1.1 → 0.95 → 1) gives it a breathing quality.
.morphing-shape {
--morph-color: #34d399;
--morph-size: 60px;
--morph-speed: 3s;
width: var(--morph-size);
height: var(--morph-size);
background: var(--morph-color);
animation: morph var(--morph-speed) ease-in-out infinite;
}
@keyframes morph {
0%, 100% {
border-radius: 50%;
transform: rotate(0deg) scale(1);
}
25% {
border-radius: 10%;
transform: rotate(90deg) scale(0.9);
}
50% {
border-radius: 50% 10%;
transform: rotate(180deg) scale(1.1);
}
75% {
border-radius: 10% 50%;
transform: rotate(270deg) scale(0.95);
}
}
<div class="morphing-shape"></div>
Tip: Use background: linear-gradient(45deg, #34d399, #3b82f6) for a gradient morph that shifts colors as it rotates.
48. Progress Bar
An animated progress bar with a shimmer overlay — shows loading progress with style.
How It Works
Two animations run simultaneously. ::before fills from width: 0 to width: 100% with forwards fill mode (one-shot). ::after creates a shimmering highlight — a transparent-to-white-to-transparent gradient that slides across continuously using translateX. The border-radius: 100px makes both the track and fill fully rounded.
.progress-bar {
--pb-color: #3b82f6;
--pb-width: 280px;
--pb-height: 8px;
--pb-speed: 2s;
width: var(--pb-width);
height: var(--pb-height);
background: rgba(255,255,255,0.1);
border-radius: 100px;
overflow: hidden;
position: relative;
}
.progress-bar::before {
content: "";
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 0;
background: var(--pb-color);
border-radius: 100px;
animation: pb-fill var(--pb-speed) ease-out forwards;
}
.progress-bar::after {
content: "";
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 60%;
background: linear-gradient(
90deg,
transparent,
rgba(255,255,255,0.3),
transparent
);
animation: pb-shimmer 1.5s ease infinite;
}
@keyframes pb-fill {
to { width: 100%; }
}
@keyframes pb-shimmer {
0% { transform: translateX(-100%); }
100% { transform: translateX(300%); }
}
<div class="progress-bar"></div>
Tip: For an indeterminate bar (unknown progress), change pb-fill to animate between left: -30% and left: 100% instead of growing the width.
49. Skeleton Loader
Shimmer skeleton placeholder — shows content layout before data loads.
How It Works
Each skeleton element (circle for avatar, lines for text) uses ::after to overlay a moving gradient. The gradient slides from left: -100% to left: 100%, creating the shimmer sweep. Different line widths (70%, 100%, 85%) simulate realistic text line lengths. The base color (#334155) is the placeholder, and the shimmer color (#475569) is slightly lighter.
.skeleton-loader {
--skel-color: #334155;
--skel-shimmer: #475569;
--skel-width: 280px;
--skel-speed: 1.5s;
width: var(--skel-width);
}
.skeleton-line {
height: 14px;
background: var(--skel-color);
border-radius: 6px;
margin-bottom: 10px;
position: relative;
overflow: hidden;
}
.skeleton-line::after {
content: "";
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(
90deg,
transparent,
var(--skel-shimmer),
transparent
);
animation: skel-shimmer var(--skel-speed) infinite;
}
.skeleton-line:nth-child(2) { width: 70%; }
.skeleton-line:nth-child(3) { width: 100%; }
.skeleton-line:nth-child(4) { width: 85%; }
.skeleton-circle {
width: 48px;
height: 48px;
border-radius: 50%;
background: var(--skel-color);
position: relative;
overflow: hidden;
margin-bottom: 10px;
}
.skeleton-circle::after {
content: "";
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(
90deg,
transparent,
var(--skel-shimmer),
transparent
);
animation: skel-shimmer var(--skel-speed) infinite;
}
@keyframes skel-shimmer {
0% { left: -100%; }
100% { left: 100%; }
}
<div class="skeleton-loader">
<div class="skeleton-circle"></div>
<div class="skeleton-line"></div>
<div class="skeleton-line"></div>
<div class="skeleton-line"></div>
</div>
Tip: Create card skeletons by combining rectangles for images, circles for avatars, and lines for text. Each piece reuses the same shimmer animation.
50. Equalizer Bars
Audio equalizer style bouncing bars — great for media and audio apps.
How It Works
Five bars of different heights animate scaleY between 0.3 and 1 with alternate direction. Each bar has a different animation-delay (0s, 0.15s, 0.3s, 0.45s, 0.6s), creating the rhythmic wave. The align-items: flex-end anchors bars to the bottom so they grow upward. border-radius: 3px on narrow bars creates rounded pill shapes.
.loading-bars {
--lb-color: #3b82f6;
--lb-speed: 1s;
display: flex;
align-items: flex-end;
gap: 4px;
height: 48px;
}
.loading-bars-bar {
width: 6px;
background: var(--lb-color);
border-radius: 3px;
animation: lb-bounce var(--lb-speed) ease-in-out infinite alternate;
}
.loading-bars-bar:nth-child(1) { height: 16px; animation-delay: 0s; }
.loading-bars-bar:nth-child(2) { height: 28px; animation-delay: 0.15s; }
.loading-bars-bar:nth-child(3) { height: 40px; animation-delay: 0.3s; }
.loading-bars-bar:nth-child(4) { height: 20px; animation-delay: 0.45s; }
.loading-bars-bar:nth-child(5) { height: 36px; animation-delay: 0.6s; }
@keyframes lb-bounce {
0% { transform: scaleY(0.3); opacity: 0.5; }
100% { transform: scaleY(1); opacity: 1; }
}
<div class="loading-bars"><div class="loading-bars-bar"></div><div class="loading-bars-bar"></div><div class="loading-bars-bar"></div><div class="loading-bars-bar"></div><div class="loading-bars-bar"></div></div>
Tip: Use a gradient background (background: linear-gradient(to top, #3b82f6, #8b5cf6)) for colorful bars.
51. Circle Spinner
A clean circular spinner with a rotating arc segment.
How It Works
Same technique as the spinning ring but with a matched color palette. The full border is a transparent version of the accent color (rgba(59, 130, 246, 0.15)), and border-top-color uses the full opacity color. This makes the track visible but subtle, with only the arc standing out.
.loading-circle {
--lc-color: #3b82f6;
--lc-size: 48px;
--lc-thickness: 4px;
--lc-speed: 1s;
width: var(--lc-size);
height: var(--lc-size);
border-radius: 50%;
border: var(--lc-thickness) solid rgba(59, 130, 246, 0.15);
border-top-color: var(--lc-color);
animation: lc-spin var(--lc-speed) linear infinite;
}
@keyframes lc-spin {
to { transform: rotate(360deg); }
}
<div class="loading-circle"></div>
Tip: For a thicker arc, add border-right-color: var(--lc-color) alongside border-top-color.
52. Bouncing Dots
Dots bouncing vertically in sequence — the messenger typing indicator.
How It Works
Three dots share the same translateY animation but with staggered delays (0s, 0.16s, 0.32s). The animation bounces each dot up by 18px at the 40% mark, then returns to baseline. The 80% hold at translateY(0) keeps the dot grounded while the others bounce — this creates the sequential typing feel.
.loading-dots-bounce {
--ldb-color: #9ca3af;
--ldb-speed: 1.4s;
display: flex;
align-items: flex-end;
gap: 6px;
height: 40px;
}
.loading-dots-bounce-dot {
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--ldb-color);
animation: ldb-bounce var(--ldb-speed) ease-in-out infinite;
}
.loading-dots-bounce-dot:nth-child(2) { animation-delay: 0.16s; }
.loading-dots-bounce-dot:nth-child(3) { animation-delay: 0.32s; }
@keyframes ldb-bounce {
0%, 80%, 100% { transform: translateY(0); }
40% { transform: translateY(-18px); }
}
<div class="loading-dots-bounce"><div class="loading-dots-bounce-dot"></div><div class="loading-dots-bounce-dot"></div><div class="loading-dots-bounce-dot"></div></div>
Tip: Wrap the dots in a chat bubble (background: #e5e7ea; border-radius: 18px; padding: 12px) for a realistic chat typing indicator.
53. Orbit Dots
Dots orbiting a center point — elegant, atomic-style spinner.
How It Works
Three dots are positioned at the top, bottom, and right of a container using absolute positioning. Each dot has a different opacity (1, 0.5, 0.75) creating a trail effect. The parent container rotates via transform: rotate(360deg), carrying all dots in a circular orbit. The opacity differences make it look like the leading dot is brighter and the trailing ones fade.
.loading-orbit {
--lo-color: #3b82f6;
--lo-size: 48px;
--lo-speed: 1.2s;
position: relative;
width: var(--lo-size);
height: var(--lo-size);
animation: lo-spin var(--lo-speed) linear infinite;
}
.loading-orbit-dot {
position: absolute;
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--lo-color);
}
.loading-orbit-dot:nth-child(1) { top: 0; left: 50%; transform: translateX(-50%); }
.loading-orbit-dot:nth-child(2) { bottom: 0; left: 50%; transform: translateX(-50%); opacity: 0.5; }
.loading-orbit-dot:nth-child(3) { top: 50%; right: 0; transform: translateY(-50%); opacity: 0.75; }
@keyframes lo-spin {
to { transform: rotate(360deg); }
}
<div class="loading-orbit"><div class="loading-orbit-dot"></div><div class="loading-orbit-dot"></div><div class="loading-orbit-dot"></div></div>
Tip: Add a center dot (<div style="width:6px;height:6px;border-radius:50%;background:var(--lo-color);position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);opacity:0.3"></div>) for an atom nucleus effect.
54. Square Flip
A 3D square flipping through space — geometric and structured.
How It Works
The square flips through four rotations: rotateX(0) rotateY(0) → rotateX(180) rotateY(0) → rotateX(180) rotateY(180) → rotateX(0) rotateY(180) → rotateX(0) rotateY(360). Each step flips one axis, creating a tumbling die effect. The perspective(200px) adds foreshortening so the square looks like it’s rotating in real 3D space.
.loading-square {
--lsq-color: #3b82f6;
--lsq-size: 36px;
--lsq-speed: 1.2s;
width: var(--lsq-size);
height: var(--lsq-size);
background: var(--lsq-color);
border-radius: 4px;
animation: lsq-flip var(--lsq-speed) ease-in-out infinite;
}
@keyframes lsq-flip {
0% { transform: perspective(200px) rotateX(0) rotateY(0); }
25% { transform: perspective(200px) rotateX(180deg) rotateY(0); }
50% { transform: perspective(200px) rotateX(180deg) rotateY(180deg); }
75% { transform: perspective(200px) rotateX(0) rotateY(180deg); }
100% { transform: perspective(200px) rotateX(0) rotateY(360deg); }
}
<div class="loading-square"></div>
Tip: Use a gradient background to make the 3D rotation more visible as colors shift during the flip.
What’s Next?
That’s 10 loading animations covering the most common patterns: spinners, dots, bars, progress, and skeletons. CSSKit has 24 total loading animations — in the next post we’ll cover the remaining 14 including DNA helix, cogs, typing indicator, and infinity loop.
Explore all 310 animations live at CSSKit or star the repo on GitHub.
You might also like
CSS Loading Animations Part 2 — 14 More Spinners & Indicators
14 more CSS loading animations with full code: wave bars, pulse ring, DNA helix, cogs, breathe, typing indicator, hamburger morph, ellipsis, clock, maze, infinity loop, jelly, gradient spinner, and fade dots.
CSS Attention Animations — 16 Effects with Code
Build 16 CSS attention animations (shake, pulse glow, rubber band, bounce, flash, heartbeat, jello, swing, pulse border, wiggle, breathe, radar, glow text, magnet, swing bounce, color flash) with live previews and copy-paste code.
CSS Background Animations — 10 Atmospheric Effects with Code
Build 10 CSS background effects from scratch: gradient shift, floating particles, aurora, ripple, bubbles, confetti, grid pulse, mesh gradient, matrix rain, and starfield. Full copy-paste code for each.
More Posts
API Gateway Patterns: The Front Door to Your Microservices
Web Components 2026: Building Framework-Agnostic UI Libraries
Building Autonomous AI Workflows with LangGraph: A Practical Guide
Building Type-Safe APIs with tRPC in 2026: Full-Stack TypeScript Without Schemas
Database Connection Pooling: Patterns for High-Performance Applications
Prompt Caching: Reduce LLM Costs by 90% with Smart Context Management
Enjoyed This Post?
Want to discuss the topic, have questions, or looking to collaborate on something similar? Drop a comment below or reach out directly.
