Skip to main content

Shimmer UI

Shimmer UI displays temporary placeholder shapes that resemble the real interface while data is loading.

Loading starts

Shimmer placeholders appear

Data arrives

Real content replaces the placeholders

One-line idea: Show the shape of the upcoming content instead of leaving the screen empty.

Core Concepts

The Problem with Plain Loading Text

A basic loading state may show only:

<p>Loading...</p>

This tells the user that something is happening, but the rest of the page remains empty.

Users may feel that:

  • the application is slow
  • nothing is happening
  • content is missing
  • the interface is incomplete

The real UI also appears suddenly when the request completes.

Blank screen

Sudden content appearance

Shimmer UI provides a more stable transition:

Placeholder layout

Real content replaces it

What Is Shimmer UI?

Shimmer UI is a loading state that imitates the structure of the real content.

Instead of rendering empty space:

<Card />
<Card />
<Card />

Render temporary placeholder cards:

<ShimmerCard />
<ShimmerCard />
<ShimmerCard />

Each placeholder represents the expected image, heading, text or action area of the real card.

Skeleton and Shimmer

The skeleton is the placeholder structure. The shimmer is the moving highlight that makes the loading state feel active.

Skeleton -> shape of the future content
Shimmer -> animated highlight across that shape

Example structure:

ShimmerCard
├── ImagePlaceholder
├── TitlePlaceholder
└── TextPlaceholder

The skeleton should closely match the dimensions and arrangement of the real component.

Dummy Cards

Dummy cards temporarily occupy the places where real cards will appear.

While loading:
[ ShimmerCard ][ ShimmerCard ][ ShimmerCard ]

After loading:
[ ProductCard ][ ProductCard ][ ProductCard ]

They do not contain real data. Their purpose is to preserve the expected page structure during loading.

Layout Stability

If the shimmer structure matches the real UI, the page does not need to rearrange dramatically when data arrives.

This helps:

  • reduce sudden layout movement
  • keep the interface visually stable
  • make the transition to real content smoother
  • show users where content will appear
Matching placeholder dimensions

Real content fits the same space

Smaller visual shift

Perceived Performance

Shimmer UI mainly improves perceived performance.

It does not make the API respond faster. It makes the waiting experience feel faster and more understandable.

While waiting, users can already see that:

  • content is being loaded
  • page sections are being prepared
  • the layout is available
  • the application is responding
Actual performancePerceived performance
How quickly the data really arrivesHow fast the application feels to the user
Controlled by network, API and processing timeImproved by clear and stable loading feedback

Practical Shimmer Patterns

Create a Reusable Shimmer Block

Start with one reusable block that provides the animated background.

ShimmerBlock.jsx
export default function ShimmerBlock({ className = "" }) {
return <div className={`shimmer-block ${className}`} />;
}
shimmer.css
.shimmer-block {
background: linear-gradient(90deg, #e5e7eb 25%, #f3f4f6 37%, #e5e7eb 63%);
background-size: 400% 100%;
animation: shimmer 1.4s ease infinite;
}

@keyframes shimmer {
0% {
background-position: 100% 0;
}

100% {
background-position: 0 0;
}
}

The gradient moves across the block and produces the shimmer effect.

Build a Card Skeleton

The shimmer card should follow the same basic structure as the real card.

ProductCardShimmer.jsx
import ShimmerBlock from "./ShimmerBlock";
import "./shimmer.css";

export default function ProductCardShimmer() {
return (
<article className="product-card product-card--shimmer">
<ShimmerBlock className="product-card__image-placeholder" />

<div className="product-card__content">
<ShimmerBlock className="product-card__title-placeholder" />
<ShimmerBlock className="product-card__text-placeholder" />
<ShimmerBlock className="product-card__text-placeholder short" />
</div>
</article>
);
}
shimmer.css
.product-card--shimmer {
width: 280px;
overflow: hidden;
border: 1px solid #e5e7eb;
border-radius: 12px;
}

.product-card__image-placeholder {
width: 100%;
height: 180px;
}

.product-card__content {
padding: 16px;
}

.product-card__title-placeholder {
width: 70%;
height: 22px;
margin-bottom: 12px;
border-radius: 4px;
}

.product-card__text-placeholder {
width: 100%;
height: 14px;
margin-bottom: 8px;
border-radius: 4px;
}

.product-card__text-placeholder.short {
width: 55%;
}

Structure comparison:

Real cardShimmer card
Product imageImage placeholder
Product titleTitle placeholder
DescriptionText placeholders

Render Multiple Dummy Cards

A loading grid should display enough placeholders to represent the incoming list.

ProductGridShimmer.jsx
import ProductCardShimmer from "./ProductCardShimmer";

export default function ProductGridShimmer({ count = 6 }) {
return (
<div className="product-grid">
{Array.from({ length: count }).map((_, index) => (
<ProductCardShimmer key={index} />
))}
</div>
);
}

The shimmer grid and real product grid should use the same layout container.

ProductGrid.jsx
export default function ProductGrid({ products }) {
return (
<div className="product-grid">
{products.map((product) => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
}

This makes the replacement feel natural because both states occupy a similar layout.

Conditional Rendering

Use loading state to choose between shimmer UI and real UI.

ProductsSection.jsx
export default function ProductsSection({ isLoading, products }) {
if (isLoading) {
return <ProductGridShimmer count={6} />;
}

return <ProductGrid products={products} />;
}
isLoading = true  -> ProductGridShimmer
isLoading = false -> ProductGrid

This is the core shimmer rendering pattern.

Complete Loading Flow

ProductsPage.jsx
import { useEffect, useState } from "react";

export default function ProductsPage() {
const [products, setProducts] = useState([]);
const [isLoading, setIsLoading] = useState(true);

useEffect(() => {
async function loadProducts() {
try {
const response = await fetch("/api/products");
const data = await response.json();
setProducts(data);
} finally {
setIsLoading(false);
}
}

loadProducts();
}, []);

return (
<main>
<h1>Products</h1>

{isLoading ? (
<ProductGridShimmer count={6} />
) : (
<ProductGrid products={products} />
)}
</main>
);
}
Component mounts

isLoading is true

Shimmer grid renders

Request completes

Products are stored

isLoading becomes false

Real product grid renders

Shimmer for a Scrollable Row

Scrollable sections should keep their row structure visible while content loads.

ScrollableRowShimmer.jsx
export default function ScrollableRowShimmer({ count = 5 }) {
return (
<section className="scrollable-section">
<ShimmerBlock className="section-title-placeholder" />

<div className="scrollable-row">
{Array.from({ length: count }).map((_, index) => (
<ProductCardShimmer key={index} />
))}
</div>
</section>
);
}
shimmer.css
.scrollable-row {
display: flex;
gap: 16px;
overflow-x: auto;
}

.scrollable-row .product-card--shimmer {
flex: 0 0 280px;
}

.section-title-placeholder {
width: 220px;
height: 28px;
margin-bottom: 16px;
border-radius: 4px;
}

Instead of showing an empty row, the placeholders indicate how many cards and what layout the user can expect.

Reusable Shimmer Components

Different real components can have matching reusable loading components.

ProductCard  -> ProductCardShimmer
Banner -> BannerShimmer
ContentRow -> RowShimmer
Profile -> ProfileShimmer

Example banner placeholder:

BannerShimmer.jsx
export default function BannerShimmer() {
return (
<section className="banner banner--shimmer">
<ShimmerBlock className="banner__title-placeholder" />
<ShimmerBlock className="banner__text-placeholder" />
</section>
);
}

Reusable shimmer components prevent every page from rebuilding the same loading structure.

Match the Real Layout

The placeholder should represent the component that will replace it.

Match these properties where possible:

  • width and height
  • card count
  • spacing and gaps
  • border radius
  • image position
  • number and approximate length of text lines
  • grid or scroll direction
Accurate skeleton

Stable replacement

Smoother loading experience

Benefits, Trade-Offs and Common Mistakes

Benefits

  • avoids an empty loading screen
  • makes progress visually understandable
  • improves perceived speed
  • keeps the page structure visible
  • reduces sudden content appearance
  • improves visual consistency during loading
  • supports reusable loading states across pages

Trade-offs

Plain loading textShimmer UI
Very simple to implementRequires placeholder components and styles
Does not show the incoming structurePreviews the expected layout
Can leave large blank areasKeeps content areas visually occupied
Creates a sudden transitionAllows a smoother replacement

Shimmer UI improves the experience while waiting, but it does not reduce the real API or network time.

Common mistakes

MistakeBetter approach
Using one generic rectangle for every screenMatch placeholders to the real component structure
Rendering a different number of shimmer cardsApproximate the expected content count
Giving placeholders different dimensionsReuse the real layout dimensions
Creating shimmer markup separately on every pageBuild reusable shimmer components
Removing the entire layout during loadingKeep grids and scrollable containers in place
Showing shimmer after data has arrivedUse loading state for clear conditional rendering
Expecting shimmer to reduce API timeTreat it as a perceived-performance improvement

Interview Revision

Quick Revision Checklist

  • Shimmer UI displays placeholder layouts while data loads.
  • Placeholder shapes should resemble the real UI.
  • Dummy cards keep lists and grids visually occupied.
  • A skeleton defines structure; shimmer provides the moving highlight.
  • Conditional rendering switches between shimmer and real content.
  • Matching dimensions helps reduce layout movement.
  • Reusable shimmer components prevent duplicated loading markup.
  • Scrollable rows should retain their structure during loading.
  • Shimmer improves perceived performance, not actual API speed.
  • Real content replaces the placeholders after loading completes.

Frequently Asked Interview Questions

1. What is Shimmer UI?

Shimmer UI is a loading pattern that renders animated placeholder shapes resembling the real interface until the actual data is available.

2. Why use shimmer instead of plain loading text?

Shimmer keeps the expected page structure visible, provides clearer loading feedback and creates a smoother transition to real content.

3. What is the difference between a skeleton and shimmer?

The skeleton is the placeholder structure. Shimmer is the animated highlight moving across that structure.

4. Does shimmer improve actual performance?

No. It does not make the network request faster. It improves perceived performance by making the application feel active and visually prepared.

5. How does shimmer reduce layout movement?

The placeholder reserves approximately the same space as the real content. When data arrives, the real component replaces a similarly sized skeleton.

6. How do you conditionally render a shimmer component?

Track a loading state. Render the shimmer component while loading is true, then render the real component after the data arrives.

7. Why create reusable shimmer components?

Reusable shimmer cards, banners and rows keep loading states consistent and prevent duplicated placeholder markup across different pages.

8. How would you create shimmer for a list?

Render multiple dummy items with the same grid or row container used by the real list. Replace them with real items when loading finishes.

9. What should a good shimmer layout match?

It should approximately match the real component's size, shape, spacing, image area and text-line structure.

Memory Trick

Shape -> Shine -> Swap

Shape the expected content with placeholders
Shine an animated gradient across them
Swap placeholders with real content when data arrives

One-Line Summary

Shimmer UI uses animated skeleton layouts to make data loading feel smoother, clearer and more stable.

Final Mental Model

Data request begins

Loading state becomes true

Skeleton components preserve the layout

Shimmer animation shows activity

Data arrives

Loading state becomes false

Real components replace the placeholders