Loading spinners look outdated. Modern sites use skeleton screens that show content placeholders while data loads. Pure CSS, no libraries needed.
The Basic Skeleton:
.skeleton {
background: linear-gradient(
90deg,
#f0f0f0 25%,
#e0e0e0 50%,
#f0f0f0 75%
);
background-size: 200% 100%;
animation: loading 1.5s infinite;
border-radius: 4px;
}
@keyframes loading {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
/* Skeleton shapes */
.skeleton-text {
height: 16px;
margin-bottom: 8px;
}
.skeleton-title {
height: 24px;
width: 60%;
margin-bottom: 16px;
}
.skeleton-avatar {
width: 48px;
height: 48px;
border-radius: 50%;
}
.skeleton-image {
width: 100%;
height: 200px;
}
HTML Usage:
<div class="card">
<!-- Show skeleton while loading -->
<div class="skeleton-avatar skeleton"></div>
<div class="skeleton-title skeleton"></div>
<div class="skeleton-text skeleton"></div>
<div class="skeleton-text skeleton" style="width: 80%"></div>
</div>
<!-- After data loads, replace with real content -->
<div class="card loaded">
<img src="avatar.jpg" alt="Avatar" />
<h2>Article Title</h2>
<p>Article content here...</p>
</div>
Advanced – Shimmer Effect:
.skeleton-shimmer {
background: linear-gradient(
90deg,
#f0f0f0 0%,
#f8f8f8 20%,
#f0f0f0 40%,
#f0f0f0 100%
);
background-size: 200% 100%;
animation: shimmer 1.5s linear infinite;
}
@keyframes shimmer {
0% {
background-position: -200% 0;
}
100% {
background-position: 200% 0;
}
}
/* Pulse effect (alternative) */
.skeleton-pulse {
animation: pulse 1.5s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.4;
}
}
Card List Skeleton:
.card-skeleton {
display: flex;
gap: 16px;
padding: 16px;
border: 1px solid #e0e0e0;
border-radius: 8px;
margin-bottom: 16px;
}
.card-skeleton-image {
flex-shrink: 0;
width: 120px;
height: 120px;
background: linear-gradient(90deg, #f0f0f0, #e0e0e0, #f0f0f0);
background-size: 200% 100%;
animation: loading 1.5s infinite;
border-radius: 4px;
}
.card-skeleton-content {
flex-grow: 1;
}
.card-skeleton-title {
height: 20px;
width: 70%;
background: linear-gradient(90deg, #f0f0f0, #e0e0e0, #f0f0f0);
background-size: 200% 100%;
animation: loading 1.5s infinite;
border-radius: 4px;
margin-bottom: 12px;
}
.card-skeleton-text {
height: 14px;
background: linear-gradient(90deg, #f0f0f0, #e0e0e0, #f0f0f0);
background-size: 200% 100%;
animation: loading 1.5s infinite;
border-radius: 4px;
margin-bottom: 8px;
}
.card-skeleton-text:last-child {
width: 60%;
}
Responsive Skeleton Grid:
.skeleton-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
}
.skeleton-card {
border: 1px solid #e0e0e0;
border-radius: 8px;
overflow: hidden;
}
.skeleton-card-image {
height: 200px;
background: linear-gradient(90deg, #f0f0f0, #e0e0e0, #f0f0f0);
background-size: 200% 100%;
animation: loading 1.5s infinite;
}
.skeleton-card-body {
padding: 16px;
}
.skeleton-card-title {
height: 24px;
width: 80%;
margin-bottom: 12px;
background: linear-gradient(90deg, #f0f0f0, #e0e0e0, #f0f0f0);
background-size: 200% 100%;
animation: loading 1.5s infinite;
border-radius: 4px;
}
.skeleton-card-text {
height: 16px;
margin-bottom: 8px;
background: linear-gradient(90deg, #f0f0f0, #e0e0e0, #f0f0f0);
background-size: 200% 100%;
animation: loading 1.5s infinite;
border-radius: 4px;
}
Dark Mode Skeleton:
@media (prefers-color-scheme: dark) {
.skeleton {
background: linear-gradient(
90deg,
#2a2a2a 25%,
#3a3a3a 50%,
#2a2a2a 75%
);
background-size: 200% 100%;
}
}
/* Or with class */
[data-theme="dark"] .skeleton {
background: linear-gradient(
90deg,
#2a2a2a 25%,
#3a3a3a 50%,
#2a2a2a 75%
);
background-size: 200% 100%;
}
Performance Benefits:
Old approach (loading spinner): - User sees blank page or spinner - Feels like site is slow - No context about what's loading Skeleton screen: - User immediately sees page structure - Understands what content is coming - Perceived load time: 30-40% faster - Actual load time: Same, but feels faster!
Best Practices:
1. Match skeleton layout to actual content - Same number of lines - Similar widths and heights - Prevents layout shift when content loads 2. Use realistic shapes - Circles for avatars - Rectangles for images - Lines for text 3. Don't overuse animation - Subtle shimmer is enough - Too much movement is distracting 4. Remove skeleton quickly - As soon as first content arrives - Progressive loading: replace parts as they load
