15 examples Responsive beginner
15 Pure CSS Loading Animations
Every animation is hand-coded from scratch using only CSS — no libraries, no JavaScript, no dependencies. Copy any snippet and drop it straight into your project.
01 / 15
House Unlock
Pure CSS A closed-front-door loader for property sites. The window stays dark while the key turns; once "loaded" (the .ready class is added), the window glows warm gold and the door opens to reveal a softly-lit room. Ideal as a real-estate page-load cover.
/* ── Stage (drop-in cover for the page) ── */
.hu-stage {
display: grid; place-items: center;
padding: 24px;
background:
radial-gradient(60% 60% at 50% 100%, rgba(212,175,55,0.10) 0%, transparent 70%),
linear-gradient(180deg, #14132b 0%, #0b0b1a 100%);
border-radius: 16px;
width: 220px; height: 220px;
}
/* ── House silhouette ── */
.hu-house {
position: relative;
width: 132px; height: 152px;
margin: 0; padding: 0;
font-family: system-ui, sans-serif;
}
.hu-roof {
position: absolute; top: 0; left: 50%;
width: 0; height: 0;
border: 30px solid transparent;
border-bottom-color: #d4af37;
transform: translateX(-50%);
filter: drop-shadow(0 4px 10px rgba(212,175,55,0.25));
}
.hu-body {
position: absolute; top: 56px; left: 6px;
width: 120px; height: 86px;
background: linear-gradient(180deg, #f5f0e6 0%, #e8e0d0 100%);
border-radius: 4px 4px 6px 6px;
box-shadow: 0 12px 28px -12px rgba(0,0,0,0.55);
overflow: hidden;
}
.hu-floor {
position: absolute; left: 0; right: 0; bottom: 0;
height: 6px;
background: #2a1f1a;
}
/* ── Window: dark while loading, warm-gold once ready ── */
.hu-window {
position: absolute; top: 14px; left: 14px;
width: 36px; height: 28px;
border: 2px solid #d4af37;
border-radius: 3px;
background: #0a0a14;
overflow: hidden;
transition: background 0.6s ease, box-shadow 0.6s ease;
}
.hu-window::before,
.hu-window::after {
content: ""; position: absolute;
background: #d4af37;
}
.hu-window::before { top: 0; bottom: 0; left: 50%; width: 1.5px; transform: translateX(-50%); }
.hu-window::after { left: 0; right: 0; top: 50%; height: 1.5px; transform: translateY(-50%); }
.hu-window-glass {
position: absolute; inset: 0;
background: radial-gradient(circle at 50% 70%, #ffd479 0%, #d4af37 50%, transparent 75%);
opacity: 0; transform: scale(0.7);
transition: opacity 0.7s ease, transform 0.7s ease;
}
/* ── Door: closed and locked, then swings inward ── */
.hu-door {
position: absolute; bottom: 6px; left: 50%;
width: 38px; height: 60px;
margin-left: -19px;
background: linear-gradient(180deg, #6b3f2a 0%, #4a2d1f 100%);
border-radius: 5px 5px 0 0;
border: 1px solid rgba(0,0,0,0.4);
transform-origin: left center;
transform: rotateY(0deg);
transition: transform 0.85s cubic-bezier(.5,0,.3,1.2);
box-shadow: inset -2px 0 0 rgba(255,255,255,0.04);
}
.hu-door-handle {
position: absolute; right: 5px; top: 50%;
width: 5px; height: 5px;
margin-top: -2.5px;
background: #d4af37;
border-radius: 50%;
box-shadow: 0 0 6px rgba(212,175,55,0.55);
}
/* ── Key: floats in, turns, then disappears ── */
.hu-key {
position: absolute; right: 10px; top: 50%;
width: 22px; height: 8px;
margin-top: -4px;
transform-origin: 18px center;
animation: huKeyTurn 2.4s cubic-bezier(.5,0,.3,1.2) infinite;
}
.hu-key-bow {
position: absolute; left: 0; top: -3px;
width: 14px; height: 14px;
border: 2px solid #d4af37;
border-radius: 50%;
background: rgba(212,175,55,0.12);
box-shadow: 0 0 8px rgba(212,175,55,0.35);
}
.hu-key-shaft {
position: absolute; left: 12px; top: 50%;
width: 9px; height: 2px;
margin-top: -1px;
background: #d4af37;
border-radius: 1px;
}
.hu-key-teeth {
position: absolute; right: 0; top: 50%;
width: 4px; height: 4px;
margin-top: -2px;
background: #d4af37;
border-radius: 0 1px 1px 0;
box-shadow: -3px 2px 0 0 #d4af37;
}
@keyframes huKeyTurn {
0%, 18% { transform: rotate(0deg) translateX(0) scale(1); opacity: 0; }
10% { opacity: 1; }
35%, 60% { transform: rotate(90deg) translateX(0) scale(1); opacity: 1; }
78% { transform: rotate(90deg) translateX(-6px) scale(0.6); opacity: 0; }
100% { transform: rotate(0deg) translateX(0) scale(1); opacity: 0; }
}
/* ── Caption ── */
.hu-caption {
position: absolute; left: 50%; bottom: -28px;
transform: translateX(-50%);
font-size: 11px; letter-spacing: 0.14em;
text-transform: uppercase; font-weight: 600;
color: #d4af37;
white-space: nowrap;
animation: huCaption 2.4s ease-in-out infinite;
}
@keyframes huCaption {
0%, 100% { opacity: 0.55; }
50% { opacity: 1; }
}
/* ── Ready state (add .ready when the page is loaded) ── */
.hu-house.ready .hu-window {
background: #ffd479;
box-shadow:
0 0 14px rgba(255,212,121,0.55),
inset 0 0 12px rgba(255,212,121,0.4);
}
.hu-house.ready .hu-window-glass { opacity: 1; transform: scale(1); }
.hu-house.ready .hu-door { transform: rotateY(-72deg); }
.hu-house.ready .hu-key,
.hu-house.ready .hu-caption { display: none; }
/* ── Drop-in usage (cover the whole page until loaded) ── */
/*
.hu-cover {
position: fixed; inset: 0; z-index: 9999;
display: grid; place-items: center;
background: #0b0b1a;
transition: opacity .6s ease, visibility .6s;
}
body.loaded .hu-cover { opacity: 0; visibility: hidden; }
*/ <div class="hu-stage">
<figure class="hu-house" aria-label="Loading your home tour">
<span class="hu-roof"></span>
<span class="hu-body">
<span class="hu-window">
<span class="hu-window-glass"></span>
</span>
<span class="hu-door">
<span class="hu-door-handle"></span>
<span class="hu-key">
<span class="hu-key-bow"></span>
<span class="hu-key-shaft"></span>
<span class="hu-key-teeth"></span>
</span>
</span>
<span class="hu-floor"></span>
</span>
<figcaption class="hu-caption">Unlocking your tour…</figcaption>
</figure>
</div> 02 / 15
Listing Card Skeleton
Pure CSS A premium listing-card skeleton that mirrors the real layout — photo, price, address, agent — then crossfades into the loaded card when `.ready` is added. Users see the page shape before content arrives, which is the modern, perceived-performance pattern used by Airbnb and Booking. Respects `prefers-reduced-motion`.
/* ── Skeleton card ── */
.lc-card {
position: relative;
display: grid; gap: 8px;
width: 240px;
padding: 10px;
background: #15151d;
border: 1px solid rgba(255,255,255,0.06);
border-radius: 14px;
font-family: system-ui, sans-serif;
}
.lc-img {
display: block; position: relative;
width: 100%; aspect-ratio: 5 / 3;
border-radius: 10px;
background: linear-gradient(135deg, #1f2433 0%, #2a3045 100%);
overflow: hidden;
}
.lc-img-shimmer {
position: absolute; inset: 0;
background: linear-gradient(90deg,
transparent 0%,
rgba(255,255,255,0.08) 50%,
transparent 100%);
transform: translateX(-100%);
animation: lcShimmer 1.6s ease-in-out infinite;
}
@keyframes lcShimmer {
100% { transform: translateX(100%); }
}
.lc-bar {
display: block; height: 10px;
background: linear-gradient(90deg, #1f2433, #2a3045, #1f2433);
background-size: 200% 100%;
border-radius: 6px;
animation: lcPulse 1.6s ease-in-out infinite;
}
@keyframes lcPulse {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
.lc-row {
display: flex; align-items: center; gap: 6px;
}
.lc-row-top { justify-content: space-between; }
.lc-bar-price { width: 82px; height: 16px; }
.lc-bar-badge { width: 56px; height: 16px; border-radius: 99px; }
.lc-bar-line { height: 9px; }
.lc-bar-w-90 { width: 90%; }
.lc-bar-w-60 { width: 60%; }
.lc-row-meta { gap: 5px; }
.lc-bar-pill { width: 48px; height: 16px; border-radius: 99px; }
.lc-row-agent { gap: 8px; padding-top: 4px; border-top: 1px solid rgba(255,255,255,0.05); margin-top: 2px; }
.lc-avatar {
display: block;
width: 24px; height: 24px;
border-radius: 50%;
background: linear-gradient(135deg, #1f2433, #2a3045);
background-size: 200% 100%;
animation: lcPulse 1.6s ease-in-out infinite;
}
.lc-bar-name { flex: 1; height: 10px; }
/* ── Loaded card (revealed when .ready) ── */
.lc-loaded {
position: absolute; inset: 0;
padding: 10px;
background: #15151d;
border-radius: 14px;
display: grid; gap: 8px;
align-content: start;
font-family: system-ui, sans-serif;
opacity: 0;
pointer-events: none;
transition: opacity 0.5s ease;
}
.lc-loaded-img {
display: block;
width: 100%; aspect-ratio: 5 / 3;
border-radius: 10px;
background:
linear-gradient(180deg, rgba(15,15,19,0) 60%, rgba(15,15,19,0.45) 100%),
linear-gradient(135deg, #5b8cb8 0%, #8aa6c0 35%, #d4b896 100%);
position: relative;
}
.lc-loaded-img::before {
content: ''; position: absolute;
bottom: 8px; left: 8px;
width: 22px; height: 22px;
background: rgba(255,255,255,0.18);
border: 1px solid rgba(255,255,255,0.4);
border-radius: 50%;
backdrop-filter: blur(6px);
}
.lc-loaded-img::after {
content: '♡'; position: absolute;
bottom: 11px; left: 14px;
font-size: 11px; color: #fff;
line-height: 1;
}
.lc-loaded-price {
font-size: 16px; font-weight: 700; color: #f0eeff;
letter-spacing: -0.01em;
display: flex; justify-content: space-between; align-items: center;
}
.lc-loaded-price::after {
content: 'For sale';
font-size: 9px; font-weight: 600;
background: rgba(46,184,138,0.18);
color: #2eb88a;
padding: 3px 8px;
border-radius: 99px;
letter-spacing: 0.06em;
text-transform: uppercase;
}
.lc-loaded-badge { display: none; }
.lc-loaded-addr {
font-size: 11px; color: #9d9bbf;
line-height: 1.4;
}
.lc-loaded-meta {
display: flex; gap: 5px; flex-wrap: wrap;
}
.lc-loaded-meta span {
font-size: 9px; font-weight: 600;
background: rgba(124,108,255,0.1);
color: #a78bfa;
padding: 3px 8px;
border-radius: 99px;
letter-spacing: 0.04em;
}
.lc-loaded-agent {
display: flex; align-items: center; gap: 8px;
padding-top: 4px;
border-top: 1px solid rgba(255,255,255,0.05);
margin-top: 2px;
}
.lc-loaded-avatar {
display: grid; place-items: center;
width: 24px; height: 24px;
border-radius: 50%;
background: linear-gradient(135deg, #d4b896, #b89970);
color: #2a1f1a;
font-size: 9px; font-weight: 800;
letter-spacing: 0.04em;
}
.lc-loaded-agent-name {
font-size: 10px; color: #c8c0ff;
}
/* ── Reveal ── */
.lc-card.ready .lc-loaded { opacity: 1; pointer-events: auto; }
.lc-card.ready > :not(.lc-loaded) { opacity: 0; }
.lc-card > :not(.lc-loaded) { transition: opacity 0.4s ease; }
/* ── Respect motion preferences ── */
@media (prefers-reduced-motion: reduce) {
.lc-img-shimmer,
.lc-bar,
.lc-avatar { animation: none; }
}
/* ── Drop-in usage ──
<article class="lc-card" aria-busy="true">
... skeleton markup ...
<span class="lc-loaded" aria-hidden="true"> ... real content ... </span>
</article>
window.addEventListener('load', () => {
document.querySelectorAll('.lc-card').forEach(c => {
c.classList.add('ready');
c.setAttribute('aria-busy', 'false');
});
});
*/ <div class="lc-card" aria-busy="true" aria-live="polite">
<span class="lc-img">
<span class="lc-img-shimmer" aria-hidden="true"></span>
</span>
<span class="lc-row lc-row-top">
<span class="lc-bar lc-bar-price"></span>
<span class="lc-bar lc-bar-badge"></span>
</span>
<span class="lc-bar lc-bar-line lc-bar-w-90"></span>
<span class="lc-bar lc-bar-line lc-bar-w-60"></span>
<span class="lc-row lc-row-meta">
<span class="lc-bar lc-bar-pill"></span>
<span class="lc-bar lc-bar-pill"></span>
<span class="lc-bar lc-bar-pill"></span>
</span>
<span class="lc-row lc-row-agent">
<span class="lc-avatar"></span>
<span class="lc-bar lc-bar-name"></span>
</span>
<span class="lc-loaded" aria-hidden="true">
<span class="lc-loaded-img"></span>
<span class="lc-loaded-price">£1,250,000</span>
<span class="lc-loaded-badge">For sale</span>
<span class="lc-loaded-addr">42 Oakwood Lane, Notting Hill</span>
<span class="lc-loaded-meta"><span>4 bed</span><span>3 bath</span><span>2,140 ft²</span></span>
<span class="lc-loaded-agent">
<span class="lc-loaded-avatar">SR</span>
<span class="lc-loaded-agent-name">Sarah Rowan · Boutique Estates</span>
</span>
</span>
</div> 03 / 15
Progress Path
Light JS An honest progress bar built on the native `<progress>` element — semantic, screen-reader-announced, and bindable to real load progress (image preloads, fetch chunks, route transitions). A moving plane glides along the path, with the percentage announced via `aria-valuenow`. Degrades gracefully: a static bar appears if JS is disabled.
/* ── Progress Path ── */
.pp-loader {
width: 240px;
display: grid; gap: 10px;
font-family: system-ui, sans-serif;
}
.pp-track {
position: relative;
height: 28px;
display: grid; align-items: center;
padding: 0 10px;
}
/* Native progress styled as an inset rail */
.pp-progress {
appearance: none; -webkit-appearance: none;
width: 100%; height: 4px;
border: 0; border-radius: 99px;
background: rgba(255,255,255,0.06);
overflow: hidden;
color: #d4af37; /* IE/Edge legacy */
}
.pp-progress::-webkit-progress-bar { background: rgba(255,255,255,0.06); border-radius: 99px; }
.pp-progress::-webkit-progress-value {
background: linear-gradient(90deg, #5b8cb8, #d4af37);
border-radius: 99px;
transition: width 0.2s ease;
}
.pp-progress::-moz-progress-bar {
background: linear-gradient(90deg, #5b8cb8, #d4af37);
border-radius: 99px;
}
/* Pins at start and end */
.pp-pin {
position: absolute; top: 50%;
width: 6px; height: 6px; margin-top: -3px;
border-radius: 50%;
background: rgba(255,255,255,0.2);
}
.pp-pin-start { left: 7px; background: #5b8cb8; box-shadow: 0 0 8px rgba(91,140,184,0.55); }
.pp-pin-end { right: 7px; background: rgba(212,175,55,0.45); }
/* Glider — the moving icon along the bar */
.pp-glider {
position: absolute; top: 50%;
left: 10px;
width: 22px; height: 22px;
margin-top: -11px;
display: grid; place-items: center;
background: linear-gradient(135deg, #ffd479, #d4af37);
color: #1a1a2e;
border-radius: 50%;
box-shadow:
0 0 0 3px rgba(212,175,55,0.18),
0 4px 14px -4px rgba(212,175,55,0.7);
transform: translateX(0);
transition: transform 0.2s ease;
}
.pp-meta {
display: flex; justify-content: space-between; align-items: center;
}
.pp-label {
font-size: 11px; letter-spacing: 0.12em;
text-transform: uppercase; font-weight: 600;
color: #9d9bbf;
}
.pp-percent {
font-family: 'JetBrains Mono', monospace;
font-size: 12px; font-weight: 700;
color: #d4af37;
letter-spacing: 0.04em;
font-variant-numeric: tabular-nums;
}
@media (prefers-reduced-motion: reduce) {
.pp-glider { transition: none; }
.pp-progress::-webkit-progress-value { transition: none; }
}
/* ── Drop-in usage (real progress) ──
<div class="pp-loader" role="status">
<div class="pp-track">
<progress class="pp-progress" value="0" max="100" aria-label="Page loading"></progress>
<span class="pp-glider"><!-- icon --></span>
<span class="pp-pin pp-pin-start"></span>
<span class="pp-pin pp-pin-end"></span>
</div>
<div class="pp-meta">
<span class="pp-label">Preparing your tour</span>
<span class="pp-percent" aria-live="polite">0%</span>
</div>
</div>
// Map to real loading work — image preloads, fetch chunks, etc.
const loader = document.querySelector('.pp-loader');
const progress = loader.querySelector('.pp-progress');
const glider = loader.querySelector('.pp-glider');
const percent = loader.querySelector('.pp-percent');
function setProgress(value) {
const v = Math.max(0, Math.min(100, value));
progress.value = v;
percent.textContent = Math.round(v) + '%';
// Move glider along the rail (track is padded 10px on each side)
const trackWidth = loader.querySelector('.pp-track').offsetWidth - 20 - 22;
glider.style.transform = 'translateX(' + (trackWidth * v / 100) + 'px)';
}
// Example: image preload progress
const images = [...]; // your URLs
let loaded = 0;
images.forEach(src => {
const img = new Image();
img.onload = img.onerror = () => {
loaded++;
setProgress((loaded / images.length) * 100);
};
img.src = src;
});
*/ <div class="pp-loader" role="status">
<div class="pp-track">
<progress class="pp-progress" value="0" max="100" aria-label="Page loading progress">0%</progress>
<span class="pp-glider" aria-hidden="true">
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<path d="M2 12l20-9-9 20-2-9-9-2z"/>
</svg>
</span>
<span class="pp-pin pp-pin-start" aria-hidden="true"></span>
<span class="pp-pin pp-pin-end" aria-hidden="true"></span>
</div>
<div class="pp-meta">
<span class="pp-label">Preparing your tour</span>
<span class="pp-percent" aria-live="polite">0%</span>
</div>
</div> 04 / 15
Heat-Map Compass
Pure CSS A premium navigation loader for property and travel platforms — a brass compass dial with cardinal points lighting one at a time as warm "heat" rings pulse outward. A monospace coordinate readout drifts below, suggesting the system is "scanning your neighbourhood". Honest narrative for indeterminate location-aware loads.
.hc-loader {
position: relative;
width: 180px; height: 180px;
margin: 0; padding: 0;
display: grid; place-items: center;
font-family: system-ui, sans-serif;
}
/* Pulsing heat rings — blue (cold) → gold (hot) */
.hc-rings {
position: absolute; inset: 0;
display: grid; place-items: center;
pointer-events: none;
}
.hc-ring {
position: absolute;
width: 84px; height: 84px;
border-radius: 50%;
border: 1.5px solid #5b8cb8;
opacity: 0;
animation: hcRing 2.6s ease-out infinite;
}
.hc-ring:nth-child(1) { animation-delay: 0s; }
.hc-ring:nth-child(2) { animation-delay: 0.85s; }
.hc-ring:nth-child(3) { animation-delay: 1.7s; }
@keyframes hcRing {
0% { transform: scale(0.6); border-color: #5b8cb8; opacity: 0; }
15% { opacity: 0.9; }
60% { border-color: #d4af37; }
100% { transform: scale(2.05); border-color: #d4af37; opacity: 0; }
}
/* Compass dial — brass ring with cardinal marks */
.hc-dial {
position: relative;
width: 96px; height: 96px;
border-radius: 50%;
background:
radial-gradient(circle at 30% 25%, rgba(255,212,121,0.12) 0%, transparent 55%),
radial-gradient(circle at 50% 50%, #1a1d2e 0%, #0f1220 70%);
border: 2px solid #d4af37;
box-shadow:
inset 0 0 18px rgba(212,175,55,0.18),
0 4px 18px -4px rgba(212,175,55,0.4),
0 0 0 1px rgba(212,175,55,0.25);
animation: hcDialSpin 18s linear infinite;
}
@keyframes hcDialSpin { to { transform: rotate(360deg); } }
/* Cardinal marks — staggered glow */
.hc-mark {
position: absolute;
font-family: 'JetBrains Mono', monospace;
font-size: 10px; font-weight: 700;
color: rgba(212,175,55,0.4);
letter-spacing: 0.06em;
transition: color 0.3s ease;
animation: hcMark 2.6s ease-in-out infinite;
}
.hc-mark-n { top: 4px; left: 50%; transform: translateX(-50%); animation-delay: 0s; }
.hc-mark-e { right: 6px; top: 50%; transform: translateY(-50%); animation-delay: 0.65s; }
.hc-mark-s { bottom: 4px; left: 50%; transform: translateX(-50%); animation-delay: 1.3s; }
.hc-mark-w { left: 6px; top: 50%; transform: translateY(-50%); animation-delay: 1.95s; }
@keyframes hcMark {
0%, 80%, 100% { color: rgba(212,175,55,0.4); text-shadow: none; }
10%, 30% { color: #ffd479; text-shadow: 0 0 8px rgba(255,212,121,0.7); }
}
/* Needle — long gold half + short slate half */
.hc-needle {
position: absolute; top: 50%; left: 50%;
width: 2px; height: 70px;
margin: -35px 0 0 -1px;
background: linear-gradient(180deg,
#ffd479 0%,
#d4af37 48%,
#2a3045 52%,
#1a1d2e 100%);
border-radius: 2px;
transform-origin: center center;
animation: hcNeedle 5s cubic-bezier(.5,.05,.3,.95) infinite;
box-shadow: 0 0 8px rgba(212,175,55,0.4);
}
@keyframes hcNeedle {
0% { transform: rotate(-22deg); }
25% { transform: rotate(80deg); }
50% { transform: rotate(-40deg); }
75% { transform: rotate(140deg); }
100% { transform: rotate(-22deg); }
}
.hc-pivot {
position: absolute; top: 50%; left: 50%;
width: 6px; height: 6px;
margin: -3px 0 0 -3px;
background: #ffd479;
border-radius: 50%;
box-shadow:
0 0 0 2px #1a1d2e,
0 0 10px rgba(255,212,121,0.6);
}
/* Coordinate readout */
.hc-coord {
position: absolute; bottom: -22px; left: 50%;
transform: translateX(-50%);
font-family: 'JetBrains Mono', monospace;
font-size: 10.5px; font-weight: 600;
color: #5eead4;
letter-spacing: 0.04em;
white-space: nowrap;
font-variant-numeric: tabular-nums;
}
@media (prefers-reduced-motion: reduce) {
.hc-ring, .hc-dial, .hc-mark, .hc-needle { animation: none; }
.hc-mark { color: #ffd479; }
}
/* ── Drop-in usage ──
<figure class="hc-loader" role="status" aria-label="Searching nearby properties">
... markup ...
</figure>
// Optional: rotate the coord readout to suggest live scanning
const coords = ['51.5074°N · 0.1278°W', '40.7128°N · 74.0060°W', '34.0522°N · 118.2437°W'];
let i = 0;
setInterval(() => {
document.querySelector('[data-hc-coord]').textContent = coords[++i % coords.length];
}, 1800);
*/ <figure class="hc-loader" role="status" aria-label="Searching nearby properties">
<span class="hc-rings" aria-hidden="true">
<span class="hc-ring"></span>
<span class="hc-ring"></span>
<span class="hc-ring"></span>
</span>
<span class="hc-dial" aria-hidden="true">
<span class="hc-mark hc-mark-n">N</span>
<span class="hc-mark hc-mark-e">E</span>
<span class="hc-mark hc-mark-s">S</span>
<span class="hc-mark hc-mark-w">W</span>
<span class="hc-needle"></span>
<span class="hc-pivot"></span>
</span>
<figcaption class="hc-coord" aria-live="polite" data-hc-coord>51.5074°N · 0.1278°W</figcaption>
</figure> 05 / 15
Floor-by-Floor
Pure CSS A vertical luxury-building loader. Floors light up bottom-to-top like an elevator passing each level, room silhouettes flicker on inside, and a penthouse glow crowns the building when `.ready` is added. Turns waiting time into an architectural narrative — perfect for residential developments and high-end real estate.
.fb-loader {
position: relative;
width: 110px; height: 200px;
margin: 0; padding: 0;
display: grid; place-items: end center;
font-family: system-ui, sans-serif;
}
/* Building silhouette */
.fb-building {
position: relative;
width: 84px;
display: flex; flex-direction: column;
border-radius: 4px 4px 0 0;
filter: drop-shadow(0 12px 24px rgba(0,0,0,0.55));
}
/* Crowning roof line */
.fb-roof {
height: 4px;
background: linear-gradient(90deg, transparent 8%, #d4af37 8%, #d4af37 92%, transparent 92%);
border-radius: 2px 2px 0 0;
margin-bottom: 1px;
}
/* Penthouse — special top floor */
.fb-penthouse {
position: relative;
height: 22px;
background: linear-gradient(180deg, #1f2433 0%, #161b2a 100%);
border: 1px solid rgba(212,175,55,0.25);
border-bottom: 0;
display: grid; place-items: center;
}
.fb-pent-light {
width: 60%; height: 8px;
border-radius: 2px;
background: rgba(255,212,121,0.08);
transition: background 0.6s ease, box-shadow 0.6s ease;
}
/* Standard floors — six identical bands */
.fb-floor {
position: relative;
height: 22px;
background: linear-gradient(180deg, #1a1f2e 0%, #14182a 100%);
border: 1px solid rgba(255,255,255,0.04);
border-bottom: 0;
display: grid; grid-template-columns: repeat(3, 1fr);
gap: 4px; padding: 4px 6px;
box-sizing: border-box;
}
/* Each "room" — a tiny window */
.fb-room {
background: rgba(255,255,255,0.03);
border-radius: 1.5px;
transition: background 0.4s ease, box-shadow 0.4s ease;
}
/* Foundation */
.fb-base {
height: 6px;
background: #2a1f1a;
border-radius: 0 0 4px 4px;
}
/* ── Lighting cycle: floors illuminate bottom-to-top ── */
.fb-floor[data-floor="1"] .fb-room { animation: fbWindow 7s ease-in-out infinite; animation-delay: 0s; }
.fb-floor[data-floor="2"] .fb-room { animation: fbWindow 7s ease-in-out infinite; animation-delay: 0.7s; }
.fb-floor[data-floor="3"] .fb-room { animation: fbWindow 7s ease-in-out infinite; animation-delay: 1.4s; }
.fb-floor[data-floor="4"] .fb-room { animation: fbWindow 7s ease-in-out infinite; animation-delay: 2.1s; }
.fb-floor[data-floor="5"] .fb-room { animation: fbWindow 7s ease-in-out infinite; animation-delay: 2.8s; }
.fb-floor[data-floor="6"] .fb-room { animation: fbWindow 7s ease-in-out infinite; animation-delay: 3.5s; }
/* Stagger rooms within a floor for life */
.fb-floor .fb-room:nth-child(2) { animation-delay: calc(var(--d, 0s) + 0.15s); }
.fb-floor .fb-room:nth-child(3) { animation-delay: calc(var(--d, 0s) + 0.3s); }
@keyframes fbWindow {
0%, 5% { background: rgba(255,255,255,0.03); box-shadow: none; }
15%, 60% {
background: #ffd479;
box-shadow:
0 0 4px rgba(255,212,121,0.55),
inset 0 0 3px rgba(255,212,121,0.45);
}
80%, 100% { background: rgba(255,255,255,0.03); box-shadow: none; }
}
/* ── Caption ── */
.fb-caption {
position: absolute; bottom: -22px; left: 50%;
transform: translateX(-50%);
font-size: 10px; letter-spacing: 0.16em;
text-transform: uppercase; font-weight: 600;
color: #d4af37;
white-space: nowrap;
animation: fbCap 2.6s ease-in-out infinite;
}
@keyframes fbCap {
0%, 100% { opacity: 0.5; }
50% { opacity: 1; }
}
/* ── Ready state — every floor lit, penthouse glowing, caption gone ── */
.fb-loader.ready .fb-room {
animation: none;
background: #ffd479;
box-shadow:
0 0 4px rgba(255,212,121,0.55),
inset 0 0 3px rgba(255,212,121,0.45);
}
.fb-loader.ready .fb-pent-light {
background: #ffd479;
box-shadow:
0 0 14px rgba(255,212,121,0.7),
inset 0 0 8px rgba(255,212,121,0.5);
}
.fb-loader.ready .fb-caption { display: none; }
@media (prefers-reduced-motion: reduce) {
.fb-room, .fb-caption { animation: none; }
.fb-room { background: rgba(255,212,121,0.45); }
}
/* ── Drop-in usage ──
<figure class="fb-loader" role="status" aria-label="Loading">
... markup ...
</figure>
window.addEventListener('load', () => {
document.querySelector('.fb-loader').classList.add('ready');
});
*/ <figure class="fb-loader" role="status" aria-label="Loading building tour">
<span class="fb-building" aria-hidden="true">
<span class="fb-roof"></span>
<span class="fb-penthouse">
<span class="fb-pent-light"></span>
</span>
<span class="fb-floor" data-floor="6"><span class="fb-room"></span><span class="fb-room"></span><span class="fb-room"></span></span>
<span class="fb-floor" data-floor="5"><span class="fb-room"></span><span class="fb-room"></span><span class="fb-room"></span></span>
<span class="fb-floor" data-floor="4"><span class="fb-room"></span><span class="fb-room"></span><span class="fb-room"></span></span>
<span class="fb-floor" data-floor="3"><span class="fb-room"></span><span class="fb-room"></span><span class="fb-room"></span></span>
<span class="fb-floor" data-floor="2"><span class="fb-room"></span><span class="fb-room"></span><span class="fb-room"></span></span>
<span class="fb-floor" data-floor="1"><span class="fb-room"></span><span class="fb-room"></span><span class="fb-room"></span></span>
<span class="fb-base"></span>
</span>
<figcaption class="fb-caption">Touring residences…</figcaption>
</figure> 06 / 15
DNA Helix
Pure CSS Alternating dots oscillate up and down in a staggered wave, mimicking the visual rhythm of a DNA double helix.
.dna { display: flex; gap: 6px; align-items: center; }
.dna-dot {
width: 12px; height: 12px; border-radius: 50%;
animation: dna 1.2s ease-in-out infinite;
}
.dna-dot:nth-child(odd) { background: #7c6cff; }
.dna-dot:nth-child(even) { background: #ff6c8a; }
.dna-dot:nth-child(1) { animation-delay: 0s; }
.dna-dot:nth-child(2) { animation-delay: 0.1s; }
.dna-dot:nth-child(3) { animation-delay: 0.2s; }
.dna-dot:nth-child(4) { animation-delay: 0.3s; }
.dna-dot:nth-child(5) { animation-delay: 0.4s; }
.dna-dot:nth-child(6) { animation-delay: 0.5s; }
@keyframes dna {
0%, 100% { transform: translateY(0) scale(1); }
50% { transform: translateY(-18px) scale(0.7); }
} <div class="dna"> <div class="dna-dot"></div> <div class="dna-dot"></div> <div class="dna-dot"></div> <div class="dna-dot"></div> <div class="dna-dot"></div> <div class="dna-dot"></div> </div>
07 / 15
Liquid Blob
Pure CSS A gradient circle morphs its border-radius through organic blob shapes, creating a fluid, living feeling.
.blob {
width: 60px; height: 60px;
background: linear-gradient(135deg, #7c6cff, #ff6c8a);
border-radius: 50%;
animation: blob 2.5s ease-in-out infinite;
}
@keyframes blob {
0%,100% { border-radius: 50% 50% 50% 50% / 50% 50% 50% 50%; }
25% { border-radius: 60% 40% 70% 30% / 40% 60% 40% 60%; }
50% { border-radius: 30% 70% 40% 60% / 60% 30% 70% 40%; }
75% { border-radius: 50% 50% 30% 70% / 30% 70% 50% 50%; }
} <div class="blob"></div>
08 / 15
Orbit System
Pure CSS Two rings rotate at different speeds and directions around a central core, like a miniature solar system.
.orbit-wrap {
position: relative; width: 60px; height: 60px;
}
.orbit-core {
position: absolute; inset: 0; margin: auto;
width: 14px; height: 14px; border-radius: 50%;
background: #7c6cff;
}
.orbit-ring {
position: absolute; inset: 0;
border: 2px solid transparent;
border-top-color: #ff6c8a;
border-radius: 50%;
animation: orbit 1s linear infinite;
}
.orbit-ring:nth-child(3) {
inset: 8px;
border-top-color: #2ecc8a;
animation-duration: 0.7s;
animation-direction: reverse;
}
@keyframes orbit { to { transform: rotate(360deg); } } <div class="orbit-wrap"> <div class="orbit-core"></div> <div class="orbit-ring"></div> <div class="orbit-ring"></div> </div>
09 / 15
Signal Bars
Pure CSS Five bars of varying heights pulse in and out of opacity with a staggered delay, like an equaliser or signal indicator.
.bars { display: flex; align-items: flex-end; gap: 5px; height: 40px; }
.bar {
width: 10px; border-radius: 3px 3px 0 0;
background: #7c6cff;
animation: bars 1s ease-in-out infinite;
}
.bar:nth-child(1) { height: 20%; animation-delay: 0s; }
.bar:nth-child(2) { height: 50%; animation-delay: 0.15s; }
.bar:nth-child(3) { height: 80%; animation-delay: 0.3s; }
.bar:nth-child(4) { height: 60%; animation-delay: 0.45s; }
.bar:nth-child(5) { height: 35%; animation-delay: 0.6s; }
@keyframes bars {
0%, 100% { opacity: 0.25; }
50% { opacity: 1; }
} <div class="bars"> <div class="bar"></div> <div class="bar"></div> <div class="bar"></div> <div class="bar"></div> <div class="bar"></div> </div>
10 / 15
Clock Sweep
Pure CSS Two pseudo-element hands rotate at different speeds inside a circular face — a literal animated clock using only CSS.
.clock {
width: 52px; height: 52px; border-radius: 50%;
border: 3px solid rgba(124,108,255,0.25);
position: relative;
}
.clock::before, .clock::after {
content: ''; position: absolute; border-radius: 2px;
}
.clock::before {
width: 2px; height: 22px; background: #7c6cff;
top: 4px; left: 50%; margin-left: -1px;
transform-origin: bottom center;
animation: chour 6s linear infinite;
}
.clock::after {
width: 2px; height: 16px; background: #ff6c8a;
top: 10px; left: 50%; margin-left: -1px;
transform-origin: bottom center;
animation: cmin 1s linear infinite;
}
@keyframes chour { to { transform: rotate(360deg); } }
@keyframes cmin { to { transform: rotate(360deg); } } <div class="clock"></div>
11 / 15
Bouncing Chain
Pure CSS Four coloured dots bounce independently with staggered delays, creating a chain-like wave of movement.
.chain { display: flex; gap: 8px; align-items: center; }
.chain-dot {
width: 14px; height: 14px; border-radius: 50%;
animation: chain 0.9s cubic-bezier(0.36,0.07,0.19,0.97) infinite;
}
.chain-dot:nth-child(1) { background: #7c6cff; animation-delay: 0s; }
.chain-dot:nth-child(2) { background: #ff6c8a; animation-delay: 0.18s; }
.chain-dot:nth-child(3) { background: #2ecc8a; animation-delay: 0.36s; }
.chain-dot:nth-child(4) { background: #f5a623; animation-delay: 0.54s; }
@keyframes chain {
0%, 100% { transform: translateY(0); }
40% { transform: translateY(-20px); }
60% { transform: translateY(-8px); }
} <div class="chain"> <div class="chain-dot"></div> <div class="chain-dot"></div> <div class="chain-dot"></div> <div class="chain-dot"></div> </div>
12 / 15
Neon Ring Draw
Pure CSS A glowing ring spinner with a layered neon box-shadow creates a vivid electric glow as it spins.
.neon-ring {
width: 56px; height: 56px; border-radius: 50%;
border: 3px solid transparent;
border-top-color: #2ecc8a;
box-shadow: 0 0 14px #2ecc8a, 0 0 28px rgba(46,204,138,0.35);
animation: nring 0.9s linear infinite;
}
.neon-ring::before {
content: ''; display: block;
width: 100%; height: 100%; border-radius: 50%;
border: 3px solid transparent;
border-bottom-color: rgba(46,204,138,0.3);
box-sizing: border-box;
}
@keyframes nring { to { transform: rotate(360deg); } } <div class="neon-ring"></div>
13 / 15
Pixel Dissolve
Pure CSS A 4×4 grid of squares fade and shrink in a rolling wave, like pixels dissolving from a screen.
.pixels { display: grid; grid-template-columns: repeat(4,12px); gap: 4px; }
.px {
width: 12px; height: 12px; border-radius: 2px;
background: #7c6cff;
animation: px 1.4s ease-in-out infinite;
}
.px:nth-child(1) { animation-delay: 0s; }
.px:nth-child(2) { animation-delay: 0.1s; }
.px:nth-child(3) { animation-delay: 0.2s; }
.px:nth-child(4) { animation-delay: 0.3s; }
.px:nth-child(5) { animation-delay: 0.4s; }
.px:nth-child(6) { animation-delay: 0.5s; }
.px:nth-child(7) { animation-delay: 0.6s; }
.px:nth-child(8) { animation-delay: 0.7s; }
.px:nth-child(9) { animation-delay: 0.8s; }
.px:nth-child(10) { animation-delay: 0.9s; }
.px:nth-child(11) { animation-delay: 1.0s; }
.px:nth-child(12) { animation-delay: 1.1s; }
.px:nth-child(13) { animation-delay: 1.2s; }
.px:nth-child(14) { animation-delay: 1.3s; }
.px:nth-child(15) { animation-delay: 0.65s; }
.px:nth-child(16) { animation-delay: 0.85s; }
@keyframes px {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0; transform: scale(0.3); }
} <div class="pixels"> <div class="px"></div><div class="px"></div> <div class="px"></div><div class="px"></div> <div class="px"></div><div class="px"></div> <div class="px"></div><div class="px"></div> <div class="px"></div><div class="px"></div> <div class="px"></div><div class="px"></div> <div class="px"></div><div class="px"></div> <div class="px"></div><div class="px"></div> </div>
14 / 15
Hourglass Flip
Pure CSS A pure CSS triangle-based hourglass that flips 180° and pauses, then repeats — built from two border triangles.
.hourglass {
width: 40px; height: 40px;
border-left: 20px solid transparent;
border-right: 20px solid transparent;
border-top: 20px solid #7c6cff;
border-bottom: 20px solid #ff6c8a;
border-radius: 4px;
animation: hflip 1.8s ease-in-out infinite;
}
@keyframes hflip {
0%,45% { transform: rotate(0deg); }
55%,100% { transform: rotate(180deg); }
} <div class="hourglass"></div>
15 / 15
Heartbeat Line
Pure CSS An SVG heartbeat waveform scrolls horizontally in a continuous loop — an inline data URI with a CSS translateX animation.
.hb-wrap { width: 120px; height: 40px; overflow: hidden; }
.hb-line {
width: 200%; height: 100%;
background: url("data:image/svg+xml,...") repeat-x center/50% 100%;
animation: hbeat 1s linear infinite;
}
@keyframes hbeat { to { transform: translateX(-50%); } } <div class="hb-wrap"> <div class="hb-line"></div> </div>
Related collections
21 CSS Button Hover Effects
21
21 original CSS button hover effects — liquid fill, glitch, neon pulse, 3D flip, particle burst and more. Pure CSS with 3 vanilla JS enhancements. Copy-paste ready.
buttons hover animation
Responsive
27 CSS Card Hover Effects
27
27 original CSS card hover effects — elastic lift, 3D tilt, holographic shimmer, spotlight, aurora and more. Pure CSS with 4 vanilla JS enhancements. Copy-paste ready.
cards hover animation
Responsive
20 CSS Link Hover Effect Designs
20
20 free CSS link hover effects — animated underlines, glitches, neon flickers, marker highlights, 3D flips and more, with copy-paste code.
css link hover link effects underline animation
Responsive