We've all been there. Clicking a button and watching a blank screen while wondering if anything is actually happening. Traditional loading spinners work, but they don't tell users what's coming next. Enter skeleton screens: the elegant solution that's become the standard for modern web applications.
A skeleton screen is a placeholder that mimics the layout and structure of your content while it loads. Instead of showing users an empty void, you're giving them a preview of what's about to appear.
Why Skeleton Screens Work
The magic of skeleton screens isn't just visual, it's psychological. When users see a structured placeholder, their brains can mentally prepare for the content that's coming. This makes the actual loading time feel shorter, even when it isn't.
Research shows that skeleton screens can improve perceived performance by up to 50%. Users report feeling less frustrated during loading states because they have context about what they're waiting for. Instead of anxiety-inducing uncertainty, they get a roadmap of the content structure.
Plus, skeleton screens scale beautifully across devices and layouts. A well-designed skeleton adapts to different screen sizes just like your real content, maintaining consistency across your entire user experience.
The Foundation: Basic Skeleton CSS
Before diving into specific components, let's establish the core CSS that powers all skeleton screens. Every skeleton shares these fundamental properties:
.skeleton {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
}
@keyframes loading {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
This creates the signature "shimmer" effect that travels across your placeholder elements. The gradient background is twice the width of the container (200% 100%
), and the animation slides it back and forth to create that polished loading appearance.
Profile Card Skeletons
Let's start with one of the most common patterns: user profile cards. These typically contain an avatar, name, and some additional information.
HTML
<div class="profile-card">
<div class="skeleton skeleton-avatar"></div>
<div class="skeleton-content">
<div class="skeleton skeleton-name"></div>
<div class="skeleton skeleton-title"></div>
</div>
</div>
CSS
.profile-card {
display: flex;
align-items: center;
padding: 20px;
border: 1px solid #eee;
border-radius: 8px;
background: white;
max-width: 400px;
}
.skeleton-avatar {
width: 60px;
height: 60px;
border-radius: 50%;
margin-right: 16px;
flex-shrink: 0;
}
.skeleton-content {
flex: 1;
}
.skeleton-name {
height: 18px;
width: 120px;
margin-bottom: 8px;
border-radius: 4px;
}
.skeleton-title {
height: 14px;
width: 90px;
border-radius: 4px;
}
The key here is matching the proportions of your real content. The avatar gets a circular shape with border-radius: 50%
, while text elements use smaller border-radius values to look like text lines. Different widths for the name and title create a more natural, less uniform appearance.
Article Card Skeletons
Content cards are everywhere in modern web design. They typically feature an image, headline, and description text. Here's how to skeleton them effectively:
HTML
<div class="article-card">
<div class="skeleton skeleton-image"></div>
<div class="skeleton skeleton-headline"></div>
<div class="skeleton skeleton-text"></div>
<div class="skeleton skeleton-text"></div>
<div class="skeleton skeleton-text short"></div>
</div>
CSS
.article-card {
padding: 20px;
border-radius: 8px;
background: white;
border: 1px solid #eee;
max-width: 350px;
}
.skeleton-image {
width: 100%;
height: 200px;
margin-bottom: 16px;
border-radius: 6px;
}
.skeleton-headline {
height: 24px;
width: 85%;
margin-bottom: 16px;
border-radius: 4px;
}
.skeleton-text {
height: 16px;
width: 100%;
margin-bottom: 8px;
border-radius: 4px;
}
.skeleton-text.short {
width: 65%;
}
The image placeholder uses a larger border-radius to match typical image styling. Multiple text lines with varying widths create the illusion of paragraph text, with the last line being shorter to mimic how real paragraphs often end.
Data Table Skeletons
Tables present unique challenges because you need to maintain column alignment and proportions. The skeleton should respect your table's structure while providing clear visual hierarchy.
HTML
<table class="skeleton-table">
<tbody>
<tr class="skeleton-row">
<td class="skeleton skeleton-cell"></td>
<td class="skeleton skeleton-cell"></td>
<td class="skeleton skeleton-cell"></td>
</tr>
<tr class="skeleton-row">
<td class="skeleton skeleton-cell"></td>
<td class="skeleton skeleton-cell"></td>
<td class="skeleton skeleton-cell"></td>
</tr>
<tr class="skeleton-row">
<td class="skeleton skeleton-cell"></td>
<td class="skeleton skeleton-cell"></td>
<td class="skeleton skeleton-cell"></td>
</tr>
</tbody>
</table>
CSS
.skeleton-table {
width: 100%;
border-collapse: collapse;
}
.skeleton-row {
border-bottom: 1px solid #eee;
}
.skeleton-cell {
padding: 12px;
height: 20px;
border-radius: 4px;
}
/* Adjust column widths to match your data */
.skeleton-cell:nth-child(1) { width: 30%; }
.skeleton-cell:nth-child(2) { width: 50%; }
.skeleton-cell:nth-child(3) { width: 20%; }
The magic is in the column width specifications. Analyze your actual table data and set skeleton widths accordingly. If your first column typically contains names, make it wider. If the last column is for status badges, make it narrow.
List Item Skeletons
Lists are perfect for skeletons because they have predictable, repeating structures. This pattern works great for notifications, messages, or any feed-style content.
HTML
<div class="skeleton-list">
<div class="skeleton-list-item">
<div class="skeleton skeleton-icon"></div>
<div class="skeleton-item-content">
<div class="skeleton skeleton-list-title"></div>
<div class="skeleton skeleton-list-subtitle"></div>
</div>
</div>
<div class="skeleton-list-item">
<div class="skeleton skeleton-icon"></div>
<div class="skeleton-item-content">
<div class="skeleton skeleton-list-title"></div>
<div class="skeleton skeleton-list-subtitle"></div>
</div>
</div>
</div>
CSS
.skeleton-list-item {
display: flex;
align-items: center;
padding: 16px 0;
border-bottom: 1px solid #eee;
}
.skeleton-icon {
width: 40px;
height: 40px;
border-radius: 6px;
margin-right: 12px;
flex-shrink: 0;
}
.skeleton-item-content {
flex: 1;
}
.skeleton-list-title {
height: 16px;
width: 70%;
margin-bottom: 6px;
border-radius: 4px;
}
.skeleton-list-subtitle {
height: 14px;
width: 50%;
border-radius: 4px;
}
The two-line structure with different widths creates a natural hierarchy. The icon placeholder uses a subtle border-radius rather than a circle, making it suitable for various icon styles or small images.
Grid Layout Skeletons
Modern web design often uses grid layouts for displaying multiple items. Your skeleton should maintain the same grid structure to provide accurate expectations.
HTML
<div class="skeleton-grid">
<div class="skeleton-card">
<div class="skeleton skeleton-card-image"></div>
<div class="skeleton skeleton-card-title"></div>
<div class="skeleton skeleton-card-text"></div>
</div>
<div class="skeleton-card">
<div class="skeleton skeleton-card-image"></div>
<div class="skeleton skeleton-card-title"></div>
<div class="skeleton skeleton-card-text"></div>
</div>
<div class="skeleton-card">
<div class="skeleton skeleton-card-image"></div>
<div class="skeleton skeleton-card-title"></div>
<div class="skeleton skeleton-card-text"></div>
</div>
</div>
CSS
.skeleton-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
}
.skeleton-card {
padding: 16px;
border: 1px solid #eee;
border-radius: 8px;
background: white;
}
.skeleton-card-image {
width: 100%;
height: 120px;
margin-bottom: 12px;
border-radius: 4px;
}
.skeleton-card-title {
height: 18px;
width: 80%;
margin-bottom: 8px;
border-radius: 4px;
}
.skeleton-card-text {
height: 14px;
width: 60%;
border-radius: 4px;
}
The auto-fit
and minmax()
functions ensure your skeleton grid behaves exactly like your real content grid, maintaining consistent spacing and breakpoints across all screen sizes.
Alternative Animation Styles
While the shimmer effect is most common, you can create different moods with alternative animations:
Pulse Animation:
.skeleton-pulse {
background: #f0f0f0;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
Wave Animation:
.skeleton-wave {
background: linear-gradient(45deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 40px 40px;
animation: wave 2s infinite linear;
}
@keyframes wave {
0% {
background-position: 0 0;
}
100% {
background-position: 40px 40px;
}
}
Pulse animations feel more subtle and work well for content-heavy applications. Wave animations create diagonal movement patterns that can feel more dynamic for creative or interactive applications.
Making Skeletons Responsive
Your skeleton screens should adapt to different screen sizes just like your real content. Use the same responsive techniques you'd use for any other component:
@media (max-width: 768px) {
.skeleton-grid {
grid-template-columns: 1fr;
}
.profile-card {
flex-direction: column;
text-align: center;
}
.skeleton-avatar {
margin: 0 0 16px 0;
}
.skeleton-name,
.skeleton-title {
width: 100%;
}
}
Dark Mode Support
Modern applications need to support both light and dark themes. Adjust your skeleton colors accordingly:
@media (prefers-color-scheme: dark) {
.skeleton {
background: linear-gradient(90deg, #2a2a2a 25%, #3a3a3a 50%, #2a2a2a 75%);
}
.skeleton-pulse {
background: #2a2a2a;
}
}
When and How to Show Skeletons
Timing is crucial for skeleton screen effectiveness. Show them immediately when a user action triggers loading, but avoid showing them for very fast requests (under 300ms) as this creates unnecessary visual noise.
Consider progressive loading where you show basic skeletons first, then replace them with more detailed ones as different data loads. This layered approach keeps users engaged throughout longer loading processes.
The goal is always to match your skeleton structure as closely as possible to your real content. Users should feel like they're watching your content materialize rather than seeing a generic placeholder transform into something completely different.