Skip to content

Bits of .NET

Daily micro-tips for C#, SQL, performance, and scalable backend engineering.

  • Asp.Net Core
  • C#
  • SQL
  • JavaScript
  • CSS
  • About
  • ErcanOPAK.com
  • No Access
  • Privacy Policy
CSS

CSS: Create Smooth Skeleton Loading Screens with Pure CSS (No JavaScript)

- 03.02.26 - ErcanOPAK

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

Related posts:

CSS Animations Cause Page Jank

CSS Layouts Break on Large Screens Only

Imitating a blink tag with CSS3 animations

Post Views: 3

Post navigation

Windows 11: Bring Back Classic Right-Click Context Menu Permanently
HTML5: Use Picture Element for Responsive Images (Better Than srcset)

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

February 2026
M T W T F S S
 1
2345678
9101112131415
16171819202122
232425262728  
« Jan    

Most Viewed Posts

  • Get the User Name and Domain Name from an Email Address in SQL (935)
  • How to add default value for Entity Framework migrations for DateTime and Bool (831)
  • Get the First and Last Word from a String or Sentence in SQL (825)
  • How to select distinct rows in a datatable in C# (799)
  • How to make theater mode the default for Youtube (714)
  • Add Constraint to SQL Table to ensure email contains @ (573)
  • How to enable, disable and check if Service Broker is enabled on a database in SQL Server (552)
  • Average of all values in a column that are not zero in SQL (519)
  • How to use Map Mode for Vertical Scroll Mode in Visual Studio (474)
  • Find numbers with more than two decimal places in SQL (436)

Recent Posts

  • C#: Use ArgumentNullException.ThrowIfNull for Cleaner Validation
  • C#: Use Discard Pattern to Ignore Unwanted Values
  • C#: Use Deconstruction with Tuples for Cleaner Multiple Returns
  • C#: Use File-Scoped Types to Limit Class Visibility
  • SQL: Use PIVOT to Transform Rows into Columns
  • SQL: Use MERGE OUTPUT to Track What Changed During Upsert
  • .NET Core: Use Polly for Resilient HTTP Requests with Retry Logic
  • .NET Core: Use Dapper for Lightweight ORM Alternative to Entity Framework
  • Git: Use git sparse-checkout to Clone Only Specific Folders
  • Git: Use git switch and git restore Instead of Confusing git checkout

Most Viewed Posts

  • Get the User Name and Domain Name from an Email Address in SQL (935)
  • How to add default value for Entity Framework migrations for DateTime and Bool (831)
  • Get the First and Last Word from a String or Sentence in SQL (825)
  • How to select distinct rows in a datatable in C# (799)
  • How to make theater mode the default for Youtube (714)

Recent Posts

  • C#: Use ArgumentNullException.ThrowIfNull for Cleaner Validation
  • C#: Use Discard Pattern to Ignore Unwanted Values
  • C#: Use Deconstruction with Tuples for Cleaner Multiple Returns
  • C#: Use File-Scoped Types to Limit Class Visibility
  • SQL: Use PIVOT to Transform Rows into Columns

Social

  • ErcanOPAK.com
  • GoodReads
  • LetterBoxD
  • Linkedin
  • The Blog
  • Twitter
© 2026 Bits of .NET | Built with Xblog Plus free WordPress theme by wpthemespace.com