Last updated: March 13, 2026
How to Create CSS Skeleton Loaders
The complete guide to building beautiful skeleton loading screens. Learn shimmer, pulse, and wave animations with accessible, production-ready code.
What Are Skeleton Loaders?
Skeleton loaders are placeholder UI elements that mimic the shape of your content while it loads. Instead of showing a blank page or generic spinner, skeleton screens give users a preview of the page structure, improving perceived performance and reducing frustration.
Skeleton Loaders
- + Show content structure preview
- + Reduce perceived loading time
- + Prevent layout shifts
- + Feel more polished and intentional
Traditional Spinners
- - No indication of content type
- - Feel slower (same duration feels longer)
- - Cause layout shifts when content loads
- - Generic and impersonal
Popular apps like Facebook, YouTube, LinkedIn, and Slack all use skeleton loaders for a smoother user experience.
Basic Skeleton Structure
Simple gray placeholder elements that match the content dimensions. No animation - just the foundation.
Live Preview
HTML + CSS Code
<div class="skeleton-container"> <div class="skeleton skeleton-text"></div> <div class="skeleton skeleton-text short"></div> <div class="skeleton skeleton-text"></div> </div>
.skeleton {
background: #e0e0e0;
border-radius: 4px;
}
.skeleton-text {
height: 16px;
margin-bottom: 12px;
}
.skeleton-text.short {
width: 60%;
}Shimmer/Wave Effect
A moving gradient that creates a shimmer or wave effect, giving the impression of loading activity.
Live Preview
HTML + CSS Code
<div class="skeleton-shimmer"> <div class="skeleton skeleton-text"></div> <div class="skeleton skeleton-text short"></div> </div>
.skeleton {
background: linear-gradient(
90deg,
#e0e0e0 25%,
#f0f0f0 50%,
#e0e0e0 75%
);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
border-radius: 4px;
}
.skeleton-text {
height: 16px;
margin-bottom: 12px;
}
.skeleton-text.short {
width: 60%;
}
@keyframes shimmer {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}Pulse/Fade Effect
A simple opacity animation that pulses between lighter and darker shades, indicating loading.
Live Preview
HTML + CSS Code
<div class="skeleton-pulse"> <div class="skeleton skeleton-text"></div> <div class="skeleton skeleton-text short"></div> </div>
.skeleton {
background: #e0e0e0;
border-radius: 4px;
animation: pulse 1.5s ease-in-out infinite;
}
.skeleton-text {
height: 16px;
margin-bottom: 12px;
}
.skeleton-text.short {
width: 60%;
}
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}Avatar/Circle Skeleton
Circular skeleton for profile pictures and avatar placeholders.
Live Preview
HTML + CSS Code
<div class="skeleton-avatar-group">
<div class="skeleton skeleton-avatar"></div>
<div class="skeleton-content">
<div class="skeleton skeleton-text name"></div>
<div class="skeleton skeleton-text subtitle"></div>
</div>
</div>.skeleton-avatar-group {
display: flex;
align-items: center;
gap: 12px;
}
.skeleton-avatar {
width: 48px;
height: 48px;
border-radius: 50%;
background: linear-gradient(
90deg,
#e0e0e0 25%,
#f0f0f0 50%,
#e0e0e0 75%
);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
.skeleton-content {
flex: 1;
}
.skeleton-text {
background: linear-gradient(
90deg,
#e0e0e0 25%,
#f0f0f0 50%,
#e0e0e0 75%
);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
border-radius: 4px;
height: 14px;
margin-bottom: 8px;
}
.skeleton-text.name {
width: 120px;
}
.skeleton-text.subtitle {
width: 80px;
margin-bottom: 0;
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}Image Placeholder Skeleton
Rectangular skeleton for image placeholders with aspect ratio preservation.
Live Preview
HTML + CSS Code
<div class="skeleton skeleton-image">
<svg class="image-icon" viewBox="0 0 24 24">
<path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/>
</svg>
</div>.skeleton-image {
width: 100%;
aspect-ratio: 16 / 9;
background: linear-gradient(
90deg,
#e0e0e0 25%,
#f0f0f0 50%,
#e0e0e0 75%
);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
}
.image-icon {
width: 48px;
height: 48px;
fill: #c0c0c0;
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}Article Card Skeleton
Complete article card skeleton with image, title, description, and metadata.
Live Preview
HTML + CSS Code
<article class="article-skeleton" aria-busy="true" role="article">
<div class="skeleton skeleton-image"></div>
<div class="article-content">
<div class="skeleton skeleton-title"></div>
<div class="skeleton skeleton-text"></div>
<div class="skeleton skeleton-text short"></div>
<div class="skeleton-meta">
<div class="skeleton skeleton-avatar-small"></div>
<div class="skeleton skeleton-text meta"></div>
</div>
</div>
</article>.article-skeleton {
background: #fff;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.skeleton {
background: linear-gradient(
90deg,
#e0e0e0 25%,
#f0f0f0 50%,
#e0e0e0 75%
);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
.skeleton-image {
width: 100%;
height: 180px;
}
.article-content {
padding: 16px;
}
.skeleton-title {
height: 24px;
border-radius: 4px;
margin-bottom: 12px;
width: 80%;
}
.skeleton-text {
height: 14px;
border-radius: 4px;
margin-bottom: 8px;
}
.skeleton-text.short {
width: 60%;
}
.skeleton-meta {
display: flex;
align-items: center;
gap: 8px;
margin-top: 16px;
}
.skeleton-avatar-small {
width: 32px;
height: 32px;
border-radius: 50%;
}
.skeleton-text.meta {
width: 100px;
margin-bottom: 0;
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}Profile Card Skeleton
Profile card with centered avatar, name, bio, and action buttons.
Live Preview
HTML + CSS Code
<div class="profile-skeleton" aria-busy="true">
<div class="skeleton skeleton-avatar-large"></div>
<div class="skeleton skeleton-name"></div>
<div class="skeleton skeleton-bio"></div>
<div class="skeleton skeleton-bio short"></div>
<div class="skeleton-buttons">
<div class="skeleton skeleton-button"></div>
<div class="skeleton skeleton-button secondary"></div>
</div>
</div>.profile-skeleton {
background: #fff;
border-radius: 16px;
padding: 24px;
text-align: center;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.skeleton {
background: linear-gradient(
90deg,
#e0e0e0 25%,
#f0f0f0 50%,
#e0e0e0 75%
);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
.skeleton-avatar-large {
width: 96px;
height: 96px;
border-radius: 50%;
margin: 0 auto 16px;
}
.skeleton-name {
height: 24px;
width: 150px;
border-radius: 4px;
margin: 0 auto 12px;
}
.skeleton-bio {
height: 14px;
width: 200px;
border-radius: 4px;
margin: 0 auto 8px;
}
.skeleton-bio.short {
width: 140px;
}
.skeleton-buttons {
display: flex;
justify-content: center;
gap: 12px;
margin-top: 20px;
}
.skeleton-button {
height: 40px;
width: 100px;
border-radius: 8px;
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}List Item Skeleton
Repeatable list item skeleton for feeds, comments, or notification lists.
Live Preview
HTML + CSS Code
<ul class="list-skeleton" aria-busy="true" role="list">
<li class="list-item-skeleton">
<div class="skeleton skeleton-avatar"></div>
<div class="list-item-content">
<div class="skeleton skeleton-text title"></div>
<div class="skeleton skeleton-text subtitle"></div>
</div>
<div class="skeleton skeleton-action"></div>
</li>
<!-- Repeat for more items -->
</ul>.list-skeleton {
list-style: none;
padding: 0;
margin: 0;
}
.list-item-skeleton {
display: flex;
align-items: center;
gap: 12px;
padding: 12px;
border-bottom: 1px solid #eee;
}
.skeleton {
background: linear-gradient(
90deg,
#e0e0e0 25%,
#f0f0f0 50%,
#e0e0e0 75%
);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
.skeleton-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
flex-shrink: 0;
}
.list-item-content {
flex: 1;
min-width: 0;
}
.skeleton-text {
border-radius: 4px;
}
.skeleton-text.title {
height: 16px;
width: 60%;
margin-bottom: 6px;
}
.skeleton-text.subtitle {
height: 12px;
width: 40%;
}
.skeleton-action {
width: 24px;
height: 24px;
border-radius: 4px;
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}Table Row Skeleton
Table skeleton for data grids and tabular content loading states.
Live Preview
HTML + CSS Code
<table class="table-skeleton" aria-busy="true">
<thead>
<tr>
<th><div class="skeleton skeleton-header"></div></th>
<th><div class="skeleton skeleton-header"></div></th>
<th><div class="skeleton skeleton-header"></div></th>
<th><div class="skeleton skeleton-header"></div></th>
</tr>
</thead>
<tbody>
<tr>
<td><div class="skeleton skeleton-cell"></div></td>
<td><div class="skeleton skeleton-cell"></div></td>
<td><div class="skeleton skeleton-cell"></div></td>
<td><div class="skeleton skeleton-cell short"></div></td>
</tr>
<!-- Repeat for more rows -->
</tbody>
</table>.table-skeleton {
width: 100%;
border-collapse: collapse;
}
.table-skeleton th,
.table-skeleton td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #eee;
}
.skeleton {
background: linear-gradient(
90deg,
#e0e0e0 25%,
#f0f0f0 50%,
#e0e0e0 75%
);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
border-radius: 4px;
}
.skeleton-header {
height: 14px;
width: 80px;
}
.skeleton-cell {
height: 16px;
width: 100%;
}
.skeleton-cell.short {
width: 60px;
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}Accessible Skeleton (Best Practice)
Production-ready skeleton with proper accessibility attributes and reduced motion support.
Live Preview
HTML + CSS Code
<div class="skeleton-container" aria-busy="true" aria-label="Loading content" role="status" > <div class="skeleton skeleton-image"></div> <div class="skeleton skeleton-text"></div> <div class="skeleton skeleton-text short"></div> <span class="sr-only">Loading...</span> </div>
/* Screen reader only class */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.skeleton-container {
padding: 16px;
}
.skeleton {
background: linear-gradient(
90deg,
#e0e0e0 25%,
#f0f0f0 50%,
#e0e0e0 75%
);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
border-radius: 4px;
}
.skeleton-image {
width: 100%;
height: 200px;
margin-bottom: 16px;
}
.skeleton-text {
height: 16px;
margin-bottom: 12px;
}
.skeleton-text.short {
width: 60%;
}
/* Reduced motion preference */
@media (prefers-reduced-motion: reduce) {
.skeleton {
animation: pulse 2s ease-in-out infinite;
}
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}Best Practices
1. Match Content Dimensions
Design skeletons to match the actual content size as closely as possible. This prevents jarring layout shifts when content loads.
/* Match your actual image container */
.skeleton-image {
width: 100%;
aspect-ratio: 16 / 9;
}2. Use Semantic ARIA
Add accessibility attributes so screen readers understand the loading state and can announce it to users.
<div aria-busy="true" role="status" aria-label="Loading content" > <span class="sr-only">Loading...</span> </div>
3. Respect Reduced Motion
Some users prefer reduced motion. Use CSS media queries to provide a simpler animation alternative.
@media (prefers-reduced-motion: reduce) {
.skeleton {
animation: pulse 2s ease-in-out infinite;
}
}4. Keep Animations Subtle
Animation should be smooth and not distracting. Use slow durations (1.5-2s) and subtle color changes.
/* Subtle shimmer colors */ background: linear-gradient( 90deg, #e0e0e0 25%, /* base */ #f0f0f0 50%, /* highlight */ #e0e0e0 75% /* base */ );
Frequently Asked Questions
A skeleton loader (also called a skeleton screen or content placeholder) is a UI pattern that shows a simplified preview of a page's layout while content is loading. Instead of a blank page or spinner, users see gray shapes that mimic the structure of the actual content, providing a better perceived performance experience.
Skeleton loaders provide better UX because they: 1) Show users what type of content is coming (setting expectations), 2) Feel faster than spinners due to perceived performance, 3) Reduce layout shift when content loads, and 4) Make the loading state feel more intentional and polished. However, spinners are still useful for quick actions or when content structure is unknown.
Shimmer (wave) animations are generally preferred because they convey more activity and feel faster. Use pulse animations when: 1) You want a subtler effect, 2) The skeleton is small or simple, or 3) You're concerned about motion sensitivity. Always respect prefers-reduced-motion for accessibility.
To make skeletons accessible: 1) Add aria-busy='true' to the loading container, 2) Use role='status' to announce loading state, 3) Include hidden text like <span class='sr-only'>Loading...</span> for screen readers, 4) Use @media (prefers-reduced-motion: reduce) to provide a simpler animation for motion-sensitive users.
Yes, ideally! Matching dimensions prevents layout shift when content loads, creating a smooth transition. If exact dimensions aren't possible, use reasonable estimates based on typical content. For dynamic content like text, show 2-3 lines of skeleton text at average width.
More CSS Tutorials
Copy CSS from Website
Extract styles with 2 clicks
Center a Div (CSS)
Flexbox, Grid, and more
Center a Div (Tailwind)
Utility classes for centering
Convert CSS to Tailwind
CSS to utility classes
Glassmorphism Effect
Frosted glass UI style
Neumorphism Button
Soft UI button design
Responsive Navbar
Mobile-friendly navigation
Sticky Header
Fixed navigation on scroll
CSS Pagination
Page navigation styles
CSS Accordion
Expandable content sections
Dropdown Menu
Hover and click dropdowns
CSS Modal
Popup dialog boxes
Toast Notifications
Animated alert messages
Hamburger Menu
Animated menu icon
Animate Gradients
Moving gradient backgrounds
Element Screenshot
Capture any element as image
Pick Color from Website
Eyedropper tool comparison
Identify Fonts
Find fonts on any website
Download All Images
Bulk save images from any site
Measure Elements
Page ruler and measurements
Extract Colors from Website
Get any color palette instantly
Dark Mode Toggle
CSS variables and localStorage
Responsive Grid
CSS Grid auto-fit and minmax
Center Text in CSS
text-align, Flexbox, and Grid
Make Text Bold
font-weight values explained
Add Shadow in CSS
box-shadow, text-shadow, drop-shadow
Round Corners in CSS
border-radius and pill shapes
Style File Input
Custom upload buttons
