react-content-loader vs react-lazy-load-image-component vs react-loading vs react-loading-skeleton vs react-placeholder
Loading States and Placeholder Components in React Applications
react-content-loaderreact-lazy-load-image-componentreact-loadingreact-loading-skeletonreact-placeholderSimilar Packages:

Loading States and Placeholder Components in React Applications

These five React packages help developers manage loading states and placeholder content in web applications, but they serve different purposes. react-content-loader and react-loading-skeleton create skeleton screens that mimic the layout of content while it loads. react-lazy-load-image-component focuses specifically on lazy-loading images with placeholder support. react-loading provides simple spinner and loading indicator components. react-placeholder offers basic placeholder functionality for content blocks. Each tool addresses different aspects of the loading experience, from visual feedback to performance optimization.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
react-content-loader014,000164 kB214 months agoMIT
react-lazy-load-image-component01,55195.4 kB57a year agoMIT
react-loading0813-198 years agoMIT
react-loading-skeleton04,20726.7 kB72 years agoMIT
react-placeholder01,612-235 years agoISC

Loading States and Placeholder Components in React: A Technical Deep-Dive

When building React applications, handling loading states well matters for user experience. These five packages tackle different parts of the loading problem. Let's compare how they work, when to use each one, and what trade-offs you'll face in real projects.

🎯 Core Purpose: What Problem Does Each Solve?

react-content-loader creates custom SVG-based skeleton screens.

  • You design the exact shape of your loading placeholder.
  • Best for matching specific UI layouts during data fetch.
// react-content-loader: Custom skeleton design
import ContentLoader from "react-content-loader"

const MyLoader = () => (
  <ContentLoader 
    speed={2}
    width={400}
    height={160}
    viewBox="0 0 400 160"
    backgroundColor="#f3f3f3"
    foregroundColor="#ecebeb"
  >
    <rect x="70" y="15" rx="4" ry="4" width="117" height="6" />
    <rect x="70" y="35" rx="3" ry="3" width="85" height="6" />
    <circle cx="30" cy="30" r="30" />
  </ContentLoader>
)

react-lazy-load-image-component handles image lazy loading with placeholders.

  • Defers image loading until visible in viewport.
  • Shows placeholder while image loads.
// react-lazy-load-image-component: Lazy image loading
import { LazyLoadImage } from 'react-lazy-load-image-component';
import 'react-lazy-load-image-component/src/effects/blur.css';

const ImageComponent = () => (
  <LazyLoadImage
    alt="Product"
    effect="blur"
    src="https://example.com/image.jpg"
    placeholderSrc="https://example.com/placeholder.jpg"
  />
);

react-loading provides pre-built spinner components.

  • Quick loading indicators without custom design.
  • Multiple spinner types out of the box.
// react-loading: Simple spinner
import Loading from 'react-loading';

const Spinner = () => (
  <Loading 
    type="spin" 
    color="#007bff" 
    height={50} 
    width={50} 
  />
);

react-loading-skeleton generates automatic skeleton loaders.

  • Wraps existing content to create matching placeholders.
  • Minimal configuration required.
// react-loading-skeleton: Auto-generated skeletons
import Skeleton from 'react-loading-skeleton';
import 'react-loading-skeleton/dist/skeleton.css';

const CardSkeleton = () => (
  <div>
    <Skeleton circle={true} height={50} width={50} />
    <Skeleton height={20} width={200} />
    <Skeleton count={3} />
  </div>
);

react-placeholder offers basic placeholder blocks.

  • Simple content placeholders with loading state.
  • Less actively maintained than newer options.
// react-placeholder: Basic placeholder
import { Placeholder } from 'react-placeholder';
import 'react-placeholder/lib/reactPlaceholder.css';

const BasicPlaceholder = ({ loading }) => (
  <Placeholder 
    loading={loading} 
    readyColor="#fff" 
    loadingColor="#f1f1f1"
  >
    <div>Your content here</div>
  </Placeholder>
);

🛠️ Customization and Control

The level of control varies significantly across these packages.

react-content-loader gives you the most control.

  • You define every shape, position, and animation detail.
  • Requires more code but matches any design exactly.
// Full control over every aspect
<ContentLoader
  speed={2}
  width={400}
  height={160}
  viewBox="0  0 400 160"
  backgroundColor="#f3f3f3"
  foregroundColor="#ecebeb"
  uniqueKey="my-loader"
>
  <rect x="0" y="0" rx="5" ry="5" width="100" height="100" />
  <rect x="120" y="20" rx="3" ry="3" width="200" height="20" />
  <rect x="120" y="60" rx="3" ry="3" width="180" height="20" />
</ContentLoader>

react-loading-skeleton balances control with simplicity.

  • You control dimensions and count, not individual shapes.
  • Faster to implement for standard layouts.
// Control dimensions, not individual shapes
<Skeleton height={300} borderRadius={8} />
<Skeleton circle={true} height={60} width={60} />
<Skeleton count={4} height={20} />

react-loading offers limited customization.

  • You pick from preset spinner types.
  • Can adjust color and size, not animation behavior.
// Limited to preset types
<Loading type="bars" color="#3498db" width={50} height={50} />
<Loading type="spinningBubbles" color="#e74c3c" />

react-lazy-load-image-component focuses on image-specific options.

  • Control placeholder, effect, and threshold.
  • Not designed for general content loading.
// Image-specific customization
<LazyLoadImage
  src="image.jpg"
  placeholderSrc="placeholder.jpg"
  effect="blur"
  threshold={100}
  wrapperProps={{ className: "image-wrapper" }}
/>

react-placeholder provides basic styling options.

  • Control colors and loading state.
  • Less flexible than skeleton-based solutions.
// Basic color and state control
<Placeholder
  loading={true}
  readyColor="#ffffff"
  loadingColor="#f0f0f0"
  style={{ padding: '20px' }}
>
  <p>Content</p>
</Placeholder>

⚡ Performance Considerations

Performance differs based on implementation approach.

react-content-loader uses SVG rendering.

  • Lightweight but adds SVG elements to DOM.
  • Animation runs via CSS, minimal JavaScript overhead.
// SVG-based, lightweight rendering
<ContentLoader viewBox="0 0 400 160">
  <rect x="0" y="0" width="400" height="160" />
</ContentLoader>
// Renders as SVG elements in DOM

react-loading-skeleton uses CSS animations on div elements.

  • No SVG overhead, pure CSS animations.
  • Slightly better performance for simple cases.
// CSS-based animations
<Skeleton height={100} />
// Renders as div with CSS animation classes

react-lazy-load-image-component optimizes image loading specifically.

  • Reduces initial page load by deferring images.
  • Can improve Core Web Vitals scores.
// Defers image loading until needed
<LazyLoadImage
  src="heavy-image.jpg"
  threshold={500} // Load 500px before entering viewport
/>

react-loading renders simple animated elements.

  • Minimal performance impact.
  • Best for short-duration loading states.
// Simple animated spinner
<Loading type="spin" height={30} width={30} />
// Minimal DOM elements, CSS animation

react-placeholder wraps content with conditional rendering.

  • Adds wrapper div during loading state.
  • Negligible performance difference in most cases.
// Conditional content rendering
<Placeholder loading={isLoading}>
  <Content />
</Placeholder>
// Swaps content based on loading prop

📦 Implementation Complexity

Setup time and learning curve vary across packages.

react-content-loader requires more initial setup.

  • You must design each skeleton shape manually.
  • Steeper learning curve but more flexible long-term.
// Requires manual shape design
const ProfileLoader = () => (
  <ContentLoader viewBox="0 0 300 150">
    <circle cx="50" cy="50" r="40" />
    <rect x="110" y="30" width="150" height="20" />
    <rect x="110" y="70" width="120" height="15" />
  </ContentLoader>
);

react-loading-skeleton works out of the box.

  • Wrap existing components, skeletons appear automatically.
  • Fastest implementation for most use cases.
// Quick implementation
{isLoading ? (
  <Skeleton count={5} />
) : (
  <Content />
)}

react-loading is the simplest to add.

  • Drop in a component, done.
  • No design decisions required.
// Simplest implementation
{isLoading && <Loading type="spin" />}

react-lazy-load-image-component requires image-specific setup.

  • Replace standard img tags with LazyLoadImage.
  • Need placeholder images prepared.
// Replace img tags throughout project
<img src="photo.jpg" /> // Before
<LazyLoadImage src="photo.jpg" /> // After

react-placeholder needs wrapping logic.

  • Wrap content blocks with Placeholder component.
  • Manage loading state props manually.
// Wrap content with loading logic
<Placeholder loading={loading}>
  <div>Content here</div>
</Placeholder>

🔄 Maintenance and Long-Term Support

Package maintenance affects long-term project health.

react-content-loader has active maintenance.

  • Regular updates and bug fixes.
  • Large community adoption provides stability.
// Actively maintained with regular updates
// Check npm for latest version before installing
npm install react-content-loader

react-loading-skeleton is well-maintained.

  • Consistent updates and good documentation.
  • Growing adoption in modern React projects.
// Good long-term support
npm install react-loading-skeleton

react-lazy-load-image-component focuses on image loading niche.

  • Maintained for specific use case.
  • Stable API, fewer breaking changes.
// Stable, niche package
npm install react-lazy-load-image-component

react-loading has basic maintenance.

  • Works for simple cases but fewer updates.
  • Consider for quick prototypes, not complex apps.
// Basic maintenance level
npm install react-loading

react-placeholder has limited recent activity.

  • Consider alternatives for new projects.
  • May lack support for modern React features.
// Limited maintenance - evaluate alternatives
npm install react-placeholder
// Consider react-loading-skeleton for new projects

🌐 Real-World Usage Scenarios

Scenario 1: E-commerce Product Cards

You're building product cards that load data from an API.

  • Best choice: react-content-loader or react-loading-skeleton
  • Why? Skeleton screens reduce perceived load time and match card layout.
// react-content-loader for precise matching
<ProductCardLoader />

// OR react-loading-skeleton for faster implementation
{loading ? <Skeleton height={300} /> : <ProductCard />}

Scenario 2: Image Gallery with Infinite Scroll

You need to load images as users scroll down the page.

  • Best choice: react-lazy-load-image-component
  • Why? Built specifically for lazy-loading images with viewport detection.
<LazyLoadImage
  src={imageUrl}
  effect="blur"
  placeholderSrc={placeholderUrl}
/>

Scenario 3: Form Submission Loading State

You need to show loading while a form submits.

  • Best choice: react-loading
  • Why? Simple spinner is sufficient for short-duration actions.
{isSubmitting && <Loading type="spin" height={24} width={24} />}

Scenario 4: Dashboard with Multiple Data Sources

You're loading widgets from different APIs at different speeds.

  • Best choice: react-loading-skeleton
  • Why? Easy to apply skeletons to multiple components quickly.
<Dashboard>
  <Skeleton loading={loading1}><Widget1 /></Skeleton>
  <Skeleton loading={loading2}><Widget2 /></Skeleton>
  <Skeleton loading={loading3}><Widget3 /></Skeleton>
</Dashboard>

Scenario 5: Legacy Application Maintenance

You're maintaining an older React application.

  • ⚠️ Consider: react-placeholder
  • Why? May already be in use, but evaluate migration to newer options.
// Existing implementation
<Placeholder loading={loading}>
  <LegacyComponent />
</Placeholder>

📊 Feature Comparison Summary

Featurereact-content-loaderreact-lazy-load-image-componentreact-loadingreact-loading-skeletonreact-placeholder
Primary UseCustom skeletonsImage lazy loadingSpinnersAuto skeletonsBasic placeholders
CustomizationHighMediumLowMediumLow
Setup TimeHighMediumLowLowLow
PerformanceGoodExcellent (images)GoodGoodGood
MaintenanceActiveActiveBasicActiveLimited
Best ForDesign systemsImage galleriesQuick loadersFast implementationLegacy projects

💡 Key Decision Factors

Choose Based on Your Loading Pattern

Content-heavy pages with complex layoutsreact-content-loader

  • You need loading states that match exact UI structure.
  • Design team requires specific loading animations.

Image-focused applicationsreact-lazy-load-image-component

  • Performance matters for image-heavy pages.
  • Need viewport-based loading triggers.

Quick prototypes or simple loadersreact-loading

  • Need loading indicators fast.
  • Don't need skeleton screens.

Standard content pagesreact-loading-skeleton

  • Want skeleton loaders without custom design work.
  • Need to implement across many components quickly.

Existing legacy codebasesreact-placeholder

  • Already using it, migration cost is high.
  • For new projects, choose modern alternatives.

🎯 Final Recommendations

For new React projects, start with react-loading-skeleton. It offers the best balance of ease-of-use and functionality for most loading state needs. The automatic skeleton generation saves development time while still providing good UX.

For design-critical applications where loading states must match brand guidelines exactly, invest in react-content-loader. The extra setup time pays off in polished, consistent loading experiences.

For image-heavy applications, react-lazy-load-image-component is essential. It solves a different problem than skeleton loaders and should be used alongside them, not instead of them.

Avoid react-placeholder for new projects unless you have specific requirements it meets that newer packages don't. The limited maintenance could become a liability as React evolves.

Remember: loading states aren't just about showing something while data loads. They're about managing user expectations and perceived performance. Choose the tool that helps you create the smoothest experience for your specific use case.

How to Choose: react-content-loader vs react-lazy-load-image-component vs react-loading vs react-loading-skeleton vs react-placeholder

  • react-content-loader:

    Choose react-content-loader when you need fully customizable skeleton loaders with precise control over the SVG shapes. It's ideal for design systems where loading states must match specific brand requirements. The package gives you complete control over animation timing, colors, and shapes, but requires more setup than automatic solutions.

  • react-lazy-load-image-component:

    Choose react-lazy-load-image-component when your primary concern is image performance and lazy loading. It's specifically built for images, not general content placeholders. Use this when you need to defer image loading until they enter the viewport while showing a placeholder during the wait.

  • react-loading:

    Choose react-loading when you need simple, ready-to-use spinner components without customization overhead. It's best for quick implementations where a standard loading indicator is sufficient. Avoid this for complex loading states that require skeleton screens or custom animations.

  • react-loading-skeleton:

    Choose react-loading-skeleton when you want skeleton loaders with minimal configuration. It automatically generates placeholders based on your content structure, making it faster to implement than react-content-loader. Ideal for projects where development speed matters more than pixel-perfect loading state design.

  • react-placeholder:

    Choose react-placeholder only for legacy projects already using it, as this package has limited maintenance and fewer features compared to modern alternatives. For new projects, consider react-loading-skeleton or react-content-loader instead, which offer better long-term support and more features.

README for react-content-loader

react-content-loader

Example's react-content-loader

SVG-Powered component to easily create placeholder loadings (like Facebook's cards loading).

Features

  • :gear: Customizable: Feel free to change the colors, speed, sizes, and even RTL;
  • :ok_hand: Plug and play: with many presets to use, see the examples;
  • :pencil2: DIY: use the create-content-loader to create your own custom loaders easily;
  • 📱 React Native support: same API, as same powerful features;
  • ⚛️ Really lightweight: less than 2kB and 0 dependencies for web version;

Index

Getting Started

npm i react-content-loader --save
yarn add react-content-loader

For React Native

npm i react-content-loader react-native-svg --save
yarn add react-content-loader react-native-svg

CDN from JSDELIVR

Usage

There are two ways to use it:

1. Presets, see the examples:

import ContentLoader, { Facebook } from 'react-content-loader'

const MyLoader = () => <ContentLoader />
const MyFacebookLoader = () => <Facebook />

2. Custom mode, see the online tool

const MyLoader = () => (
  <ContentLoader viewBox="0 0 380 70">
    {/* Only SVG shapes */}    
    <rect x="0" y="0" rx="5" ry="5" width="70" height="70" />
    <rect x="80" y="17" rx="4" ry="4" width="300" height="13" />
    <rect x="80" y="40" rx="3" ry="3" width="250" height="10" />
  </ContentLoader>
)

Still not clear? Take a look at this working example at codesandbox.io Or try the components editable demo hands-on and install it from bit.dev

Native

react-content-loader can be used with React Native in the same way as web version with the same import:

1. Presets, see the examples:

import ContentLoader, { Facebook } from 'react-content-loader/native'

const MyLoader = () => <ContentLoader />
const MyFacebookLoader = () => <Facebook />

2. Custom mode

To create custom loaders there is an important difference: as React Native doesn't have any native module for SVG components, it's necessary to import the shapes from react-native-svg or use the named export Rect and Circle from react-content-loader import:

import ContentLoader, { Rect, Circle } from 'react-content-loader/native'

const MyLoader = () => (
  <ContentLoader viewBox="0 0 380 70">
    <Circle cx="30" cy="30" r="30" />
    <Rect x="80" y="17" rx="4" ry="4" width="300" height="13" />
    <Rect x="80" y="40" rx="3" ry="3" width="250" height="10" />
  </ContentLoader>
)

Options

Prop name and type
EnvironmentDescription
animate?: boolean
Defaults to true
React DOM
React Native
Opt-out of animations with false
title?: string
Defaults to Loading...
React DOM onlyIt's used to describe what element it is. 
Use '' (empty string) to remove.
baseUrl?: string
Defaults to an empty string
React DOM onlyRequired if you're using <base url="/" /> document <head/>
This prop is common used as: 
<ContentLoader baseUrl={window.location.pathname} /> which will fill the SVG attribute with the relative path. Related #93.
speed?: number
Defaults to 1.2
React DOM
React Native
Animation speed in seconds.
viewBox?: string
Defaults to undefined
React DOM
React Native
Use viewBox props to set a custom viewBox value,
for more information about how to use it,
read the article How to Scale SVG.
gradientRatio?: number
Defaults to 1.2
React DOM onlyWidth of the animated gradient as a fraction of the view box width.
rtl?: boolean
Defaults to false
React DOM
React Native
Content right-to-left.
backgroundColor?: string
Defaults to #f5f6f7
React DOM
React Native
Used as background of animation.
foregroundColor?: string
Defaults to #eee
React DOM
React Native
Used as the foreground of animation.
backgroundOpacity?: number
Defaults to 1
React DOM
React Native
Background opacity (0 = transparent, 1 = opaque)
used to solve an issue in Safari
foregroundOpacity?: number
Defaults to 1
React DOM
React Native
Animation opacity (0 = transparent, 1 = opaque)
used to solve an issue in Safari
style?: React.CSSProperties
Defaults to {}
React DOM only
uniqueKey?: string
Defaults to random unique id
React DOM onlyUse the same value of prop key, 
that will solve inconsistency on the SSR, see more here.
beforeMask?: JSX.Element
Defaults to null
React DOM
React Native
Define custom shapes before content, 
see more here.

See all options live

Examples

Facebook Style
import { Facebook } from 'react-content-loader'

const MyFacebookLoader = () => <Facebook />
Facebook Style
Instagram Style
import { Instagram } from 'react-content-loader'

const MyInstagramLoader = () => <Instagram />
Instagram Style
Code Style
import { Code } from 'react-content-loader'

const MyCodeLoader = () => <Code />
Code Style
List Style
import { List } from 'react-content-loader'

const MyListLoader = () => <List />
List Style
Bullet list Style
import { BulletList } from 'react-content-loader'

const MyBulletListLoader = () => <BulletList />
Bullet list Style

Custom Style

For the custom mode, use the online tool.

const MyLoader = () => (
  <ContentLoader
    height={140}
    speed={1}
    backgroundColor={'#333'}
    foregroundColor={'#999'}
    viewBox="0 0 380 70"
  >
    {/* Only SVG shapes */}
    <rect x="0" y="0" rx="5" ry="5" width="70" height="70" />
    <rect x="80" y="17" rx="4" ry="4" width="300" height="13" />
    <rect x="80" y="40" rx="3" ry="3" width="250" height="10" />
  </ContentLoader>
)

Custom

Troubleshooting

Responsive - Mobile version

In order to avoid unexpected behavior, the package doesn't have opinioned settings. So if it needs to be responsive, have in mind that the output of the package is a regular SVG, so it just needs the same attributes to become a regular SVG responsive, which means:

import { Code } from 'react-content-loader'

const MyCodeLoader = () => (
  <Code
    width={100}
    height={100}
    viewBox="0 0 100 100"
    style={{ width: '100%' }}
  />
)

Server-side rendering (SSR) - Match snapshot

As the main component generates random values to match the id of the SVG element with background style, it can encounter unexpected errors and unmatching warning on render, once the random value of id will be generated twice, in case of SSR: server and client; or in case of snapshot test: on the first match and re-running the test.

To fix it, set the prop uniqueKey, then the id will not be random anymore:

import { Facebook } from 'react-content-loader'

const MyFacebookLoader = () => <Facebook uniqueKey="my-random-value" />

Alpha is not working: Safari / iOS

When using rgba as a backgroundColor or foregroundColor value, Safari does not respect the alpha channel, meaning that the color will be opaque. To prevent this, instead of using a rgba value for backgroundColor/foregroundColor, use the rgb equivalent and move the alpha channel value to the backgroundOpacity/foregroundOpacity props.

{/* Opaque color in Safari and iOS */}
<ContentLoader
  backgroundColor="rgba(0,0,0,0.06)"
  foregroundColor="rgba(0,0,0,0.12)">


{/_ Semi-transparent color in Safari and iOS _/}
<ContentLoader
    backgroundColor="rgb(0,0,0)"
    foregroundColor="rgb(0,0,0)"
    backgroundOpacity={0.06}
    foregroundOpacity={0.12}>


Black box in Safari / iOS (again)

Using the base tag on a page that contains SVG elements fails to render and it looks like a black box. Just remove the base-href tag from the <head /> and the issue has been solved.

black box

See: #93 / 109

Browser supports SVG-Animate

Old browsers don't support animation in SVG (compatibility list), and if your project must support IE, for examples, here's a couple of ways to make sure that browser supports SVG Animate:

  • window.SVGAnimateElement
  • document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#SVG-Animation", "1.1")
  • Or even use https://modernizr.com/

Similar packages


Development

Fork the repo and then clone it

$ git clone git@github.com:YourUsername/react-content-loader.git && cd react-content-loader

$ npm i: Install the dependencies;

$ npm run build: Build to production;

$ npm run dev: Run the Storybook to see your changes;

$ npm run test: Run all tests: type checking, unit tests on web and native;

$ npm run test:watch: Watch unit tests;

React Native

As React Native doesn't support symbolic links (to link the dependency to another folder) and as there is no playground to check your contributions (like storybook), this is recommended strategy to run the project locally:

  1. Create a new React Native from scratch, either Metro or create-react-native-app;
  2. Install the dependency to your root project: yarn add react-content-loader react-native-svg
  3. Open the project just created and clone this repository there;
  4. Create your loading component and point the react-content-loader to the project just cloned, like: import ContentLoader, { Rect, Circle } from './react-content-loader/native'

Commit messages

Commit messages should follow the commit message convention so, changelogs could be generated automatically by that. Commit messages are validated automatically upon commit. If you aren't familiar with the commit message convention, you can use yarn commit (or npm run commit) instead of git commit, which provides an interactive CLI for generating proper commit messages.

License

MIT