UI Components Easy
Loading Variants
Six CSS-only loading animations — spinner, dots, bar, pulse, bounce, and ring — in multiple sizes.
Open in Lab
MCP
css
Targets: HTML
Code
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: Inter, system-ui, sans-serif;
background: #050910;
color: #f2f6ff;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
}
.demo {
width: 100%;
max-width: 680px;
text-align: center;
}
.demo-title {
font-size: 1.5rem;
font-weight: 800;
margin-bottom: 0.375rem;
}
.demo-sub {
color: #475569;
font-size: 0.875rem;
margin-bottom: 2.5rem;
}
/* ── Layout ── */
.loader-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 1rem;
}
.loader-group {
background: #0d1320;
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 1.125rem;
padding: 1.5rem 1rem 1.25rem;
display: flex;
flex-direction: column;
align-items: center;
gap: 1.25rem;
}
.group-label {
font-size: 0.6875rem;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
color: #334155;
}
.row {
display: flex;
align-items: center;
justify-content: center;
gap: 1.25rem;
}
.col {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 0.625rem;
width: 100%;
}
/* ── Accent color ── */
:root {
--accent: #38bdf8;
}
/* ========================================
SPINNER
======================================== */
.spinner {
border-radius: 50%;
border-style: solid;
border-color: rgba(56, 189, 248, 0.2);
border-top-color: var(--accent);
animation: spin 0.75s linear infinite;
flex-shrink: 0;
}
.spinner.sm {
width: 16px;
height: 16px;
border-width: 2px;
}
.spinner.md {
width: 26px;
height: 26px;
border-width: 3px;
}
.spinner.lg {
width: 42px;
height: 42px;
border-width: 4px;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
/* ========================================
DOTS
======================================== */
.dots {
display: flex;
align-items: center;
gap: 5px;
}
.dots span {
border-radius: 50%;
background: var(--accent);
animation: dotFade 1.2s ease-in-out infinite;
flex-shrink: 0;
}
.dots span:nth-child(2) {
animation-delay: 0.2s;
}
.dots span:nth-child(3) {
animation-delay: 0.4s;
}
.dots.sm span {
width: 5px;
height: 5px;
}
.dots.md span {
width: 8px;
height: 8px;
}
.dots.lg span {
width: 13px;
height: 13px;
}
@keyframes dotFade {
0%,
80%,
100% {
opacity: 0.2;
transform: scale(0.8);
}
40% {
opacity: 1;
transform: scale(1);
}
}
/* ========================================
BAR
======================================== */
.bar-loader {
background: rgba(56, 189, 248, 0.12);
border-radius: 999px;
overflow: hidden;
width: 100%;
}
.bar-loader.sm {
height: 4px;
}
.bar-loader.md {
height: 6px;
}
.bar-loader.lg {
height: 10px;
}
.bar-fill {
height: 100%;
width: 40%;
background: linear-gradient(90deg, transparent, var(--accent), transparent);
border-radius: 999px;
animation: barSlide 1.4s ease-in-out infinite;
}
@keyframes barSlide {
0% {
transform: translateX(-150%);
}
100% {
transform: translateX(350%);
}
}
/* ========================================
PULSE
======================================== */
.pulse {
border-radius: 50%;
background: var(--accent);
position: relative;
flex-shrink: 0;
animation: pulseScale 1.5s ease-in-out infinite;
}
.pulse.sm {
width: 14px;
height: 14px;
}
.pulse.md {
width: 22px;
height: 22px;
}
.pulse.lg {
width: 36px;
height: 36px;
}
.pulse::after {
content: "";
position: absolute;
inset: 0;
border-radius: 50%;
background: var(--accent);
animation: pulseRing 1.5s ease-out infinite;
}
@keyframes pulseScale {
0%,
100% {
transform: scale(0.9);
opacity: 0.8;
}
50% {
transform: scale(1);
opacity: 1;
}
}
@keyframes pulseRing {
0% {
transform: scale(1);
opacity: 0.6;
}
100% {
transform: scale(2.4);
opacity: 0;
}
}
/* ========================================
BOUNCE
======================================== */
.bounce {
display: flex;
align-items: flex-end;
gap: 4px;
height: auto;
}
.bounce span {
border-radius: 50%;
background: var(--accent);
animation: bounceUp 0.9s ease-in-out infinite alternate;
flex-shrink: 0;
}
.bounce span:nth-child(2) {
animation-delay: 0.15s;
}
.bounce span:nth-child(3) {
animation-delay: 0.3s;
}
.bounce.sm span {
width: 5px;
height: 5px;
}
.bounce.md span {
width: 8px;
height: 8px;
}
.bounce.lg span {
width: 13px;
height: 13px;
}
@keyframes bounceUp {
from {
transform: translateY(0);
opacity: 0.5;
}
to {
transform: translateY(-10px);
opacity: 1;
}
}
/* ========================================
RING
======================================== */
.ring {
border-radius: 50%;
border-style: solid;
border-color: rgba(56, 189, 248, 0.25);
position: relative;
flex-shrink: 0;
}
.ring.sm {
width: 16px;
height: 16px;
border-width: 2px;
}
.ring.md {
width: 26px;
height: 26px;
border-width: 3px;
}
.ring.lg {
width: 42px;
height: 42px;
border-width: 4px;
}
.ring::before,
.ring::after {
content: "";
position: absolute;
inset: -1px;
border-radius: 50%;
border: inherit;
border-color: transparent;
}
.ring::before {
border-top-color: var(--accent);
animation: spin 1s linear infinite;
}
.ring::after {
border-bottom-color: rgba(56, 189, 248, 0.5);
animation: spin 1s linear infinite reverse;
animation-delay: -0.25s;
}
/* ========================================
Reduced motion
======================================== */
@media (prefers-reduced-motion: reduce) {
.spinner,
.dots span,
.bar-fill,
.pulse,
.pulse::after,
.bounce span,
.ring::before,
.ring::after {
animation: none;
}
.pulse {
opacity: 1;
}
.bar-fill {
width: 100%;
}
}<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Loading Variants</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="demo">
<h1 class="demo-title">Loading Variants</h1>
<p class="demo-sub">Six CSS-only animations — three sizes each. Zero JavaScript.</p>
<div class="loader-grid">
<!-- Spinner -->
<div class="loader-group">
<span class="group-label">Spinner</span>
<div class="row" role="status" aria-label="Loading">
<div class="spinner sm"></div>
<div class="spinner md"></div>
<div class="spinner lg"></div>
</div>
</div>
<!-- Dots -->
<div class="loader-group">
<span class="group-label">Dots</span>
<div class="row" role="status" aria-label="Loading">
<div class="dots sm"><span></span><span></span><span></span></div>
<div class="dots md"><span></span><span></span><span></span></div>
<div class="dots lg"><span></span><span></span><span></span></div>
</div>
</div>
<!-- Bar -->
<div class="loader-group">
<span class="group-label">Bar</span>
<div class="col" role="status" aria-label="Loading">
<div class="bar-loader sm"><div class="bar-fill"></div></div>
<div class="bar-loader md"><div class="bar-fill"></div></div>
<div class="bar-loader lg"><div class="bar-fill"></div></div>
</div>
</div>
<!-- Pulse -->
<div class="loader-group">
<span class="group-label">Pulse</span>
<div class="row" role="status" aria-label="Loading">
<div class="pulse sm"></div>
<div class="pulse md"></div>
<div class="pulse lg"></div>
</div>
</div>
<!-- Bounce -->
<div class="loader-group">
<span class="group-label">Bounce</span>
<div class="row" role="status" aria-label="Loading">
<div class="bounce sm"><span></span><span></span><span></span></div>
<div class="bounce md"><span></span><span></span><span></span></div>
<div class="bounce lg"><span></span><span></span><span></span></div>
</div>
</div>
<!-- Ring -->
<div class="loader-group">
<span class="group-label">Ring</span>
<div class="row" role="status" aria-label="Loading">
<div class="ring sm"></div>
<div class="ring md"></div>
<div class="ring lg"></div>
</div>
</div>
</div>
</div>
</body>
</html>Loading Variants
Six distinct CSS-only loading animations, each available in three sizes (sm, md, lg). Use them as drop-in replacements for any loading state — buttons, overlays, skeletons, or full pages.
Variants
| Name | Animation |
|---|---|
| Spinner | Rotating circle with a colored arc |
| Dots | Three dots fading in and out in sequence |
| Bar | Horizontal progress bar with infinite fill |
| Pulse | Expanding ring pulse from a filled circle |
| Bounce | Three balls bouncing with stagger delay |
| Ring | Dual-ring counter-rotation |
Sizes
Each variant accepts a --size modifier class:
.sm— 16 px (or equivalent).md— 24 px (default).lg— 40 px
Accessibility
- Wrap each loader in a container with
role="status"and anaria-labeldescribing the loading action - Loaders use
@media (prefers-reduced-motion: reduce)to pause animations for users who prefer less motion