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.
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.
react-content-loader creates custom SVG-based skeleton screens.
// 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.
// 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.
// 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.
// 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.
// 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>
);
The level of control varies significantly across these packages.
react-content-loader gives you the most control.
// 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.
// 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.
// 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.
// Image-specific customization
<LazyLoadImage
src="image.jpg"
placeholderSrc="placeholder.jpg"
effect="blur"
threshold={100}
wrapperProps={{ className: "image-wrapper" }}
/>
react-placeholder provides basic styling options.
// Basic color and state control
<Placeholder
loading={true}
readyColor="#ffffff"
loadingColor="#f0f0f0"
style={{ padding: '20px' }}
>
<p>Content</p>
</Placeholder>
Performance differs based on implementation approach.
react-content-loader uses SVG rendering.
// 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.
// CSS-based animations
<Skeleton height={100} />
// Renders as div with CSS animation classes
react-lazy-load-image-component optimizes image loading specifically.
// Defers image loading until needed
<LazyLoadImage
src="heavy-image.jpg"
threshold={500} // Load 500px before entering viewport
/>
react-loading renders simple animated elements.
// Simple animated spinner
<Loading type="spin" height={30} width={30} />
// Minimal DOM elements, CSS animation
react-placeholder wraps content with conditional rendering.
// Conditional content rendering
<Placeholder loading={isLoading}>
<Content />
</Placeholder>
// Swaps content based on loading prop
Setup time and learning curve vary across packages.
react-content-loader requires more initial setup.
// 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.
// Quick implementation
{isLoading ? (
<Skeleton count={5} />
) : (
<Content />
)}
react-loading is the simplest to add.
// Simplest implementation
{isLoading && <Loading type="spin" />}
react-lazy-load-image-component requires image-specific setup.
// Replace img tags throughout project
<img src="photo.jpg" /> // Before
<LazyLoadImage src="photo.jpg" /> // After
react-placeholder needs wrapping logic.
// Wrap content with loading logic
<Placeholder loading={loading}>
<div>Content here</div>
</Placeholder>
Package maintenance affects long-term project health.
react-content-loader has active maintenance.
// Actively maintained with regular updates
// Check npm for latest version before installing
npm install react-content-loader
react-loading-skeleton is well-maintained.
// Good long-term support
npm install react-loading-skeleton
react-lazy-load-image-component focuses on image loading niche.
// Stable, niche package
npm install react-lazy-load-image-component
react-loading has basic maintenance.
// Basic maintenance level
npm install react-loading
react-placeholder has limited recent activity.
// Limited maintenance - evaluate alternatives
npm install react-placeholder
// Consider react-loading-skeleton for new projects
You're building product cards that load data from an API.
react-content-loader or react-loading-skeleton// react-content-loader for precise matching
<ProductCardLoader />
// OR react-loading-skeleton for faster implementation
{loading ? <Skeleton height={300} /> : <ProductCard />}
You need to load images as users scroll down the page.
react-lazy-load-image-component<LazyLoadImage
src={imageUrl}
effect="blur"
placeholderSrc={placeholderUrl}
/>
You need to show loading while a form submits.
react-loading{isSubmitting && <Loading type="spin" height={24} width={24} />}
You're loading widgets from different APIs at different speeds.
react-loading-skeleton<Dashboard>
<Skeleton loading={loading1}><Widget1 /></Skeleton>
<Skeleton loading={loading2}><Widget2 /></Skeleton>
<Skeleton loading={loading3}><Widget3 /></Skeleton>
</Dashboard>
You're maintaining an older React application.
react-placeholder// Existing implementation
<Placeholder loading={loading}>
<LegacyComponent />
</Placeholder>
| Feature | react-content-loader | react-lazy-load-image-component | react-loading | react-loading-skeleton | react-placeholder |
|---|---|---|---|---|---|
| Primary Use | Custom skeletons | Image lazy loading | Spinners | Auto skeletons | Basic placeholders |
| Customization | High | Medium | Low | Medium | Low |
| Setup Time | High | Medium | Low | Low | Low |
| Performance | Good | Excellent (images) | Good | Good | Good |
| Maintenance | Active | Active | Basic | Active | Limited |
| Best For | Design systems | Image galleries | Quick loaders | Fast implementation | Legacy projects |
Content-heavy pages with complex layouts → react-content-loader
Image-focused applications → react-lazy-load-image-component
Quick prototypes or simple loaders → react-loading
Standard content pages → react-loading-skeleton
Existing legacy codebases → react-placeholder
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.
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.
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.
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.
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.
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.
Make beautiful, animated loading skeletons that automatically adapt to your app.
Learn about the changes in version 3, or view the v2 documentation.
Install via one of:
yarn add react-loading-skeleton
npm install react-loading-skeleton
import Skeleton from 'react-loading-skeleton'
import 'react-loading-skeleton/dist/skeleton.css'
<Skeleton /> // Simple, single-line loading skeleton
<Skeleton count={5} /> // Five-line loading skeleton
The Skeleton component should be used directly in your components in place of
content that is loading. While other libraries require you to meticulously craft
a skeleton screen that matches the font size, line height, and margins of your
content, the Skeleton component is automatically sized to the correct
dimensions.
For example:
function BlogPost(props) {
return (
<div>
<h1>{props.title || <Skeleton />}</h1>
{props.body || <Skeleton count={10} />}
</div>
);
}
...will produce correctly-sized skeletons for the heading and body without any further configuration.
This ensures the loading state remains up-to-date with any changes to your layout or typography.
Instead, make components with built-in skeleton states.
This approach is beneficial because:
Customize individual skeletons with props, or render a SkeletonTheme to style
all skeletons below it in the React hierarchy:
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
return (
<SkeletonTheme baseColor="#202020" highlightColor="#444">
<p>
<Skeleton count={3} />
</p>
</SkeletonTheme>
);
Skeleton only| Prop | Description | Default |
|---|---|---|
count?: number |
The number of lines of skeletons to render. If
count is a decimal number like 3.5,
three full skeletons and one half-width skeleton will be
rendered.
| 1 |
wrapper?: React.FunctionComponent | A custom wrapper component that goes around the individual skeleton elements. | |
circle?: boolean |
Makes the skeleton circular by setting border-radius to
50%.
| false |
className?: string |
A custom class name for the individual skeleton elements which is used
alongside the default class, react-loading-skeleton.
| |
containerClassName?: string |
A custom class name for the <span> that wraps the
individual skeleton elements.
| |
containerTestId?: string |
A string that is added to the container element as a
data-testid attribute. Use it with
screen.getByTestId('...') from React Testing Library.
| |
style?: React.CSSProperties |
This is an escape hatch for advanced use cases and is not the preferred
way to style the skeleton. Props (e.g. width,
borderRadius) take priority over this style object.
|
Skeleton and SkeletonTheme| Prop | Description | Default |
|---|---|---|
baseColor?: string | The background color of the skeleton. | #ebebeb |
highlightColor?: string | The highlight color in the skeleton animation. | #f5f5f5 |
width?: string | number | The width of the skeleton. | 100% |
height?: string | number | The height of each skeleton line. | The font size |
borderRadius?: string | number | The border radius of the skeleton. | 0.25rem |
inline?: boolean |
By default, a <br /> is inserted after each skeleton so
that each skeleton gets its own line. When inline is true, no
line breaks are inserted.
| false |
duration?: number | The length of the animation in seconds. | 1.5 |
direction?: 'ltr' | 'rtl' | The direction of the animation, either left-to-right or right-to-left. | 'ltr' |
enableAnimation?: boolean |
Whether the animation should play. The skeleton will be a solid color when
this is false. You could use this prop to stop the animation
if an error occurs.
| true |
customHighlightBackground?: string |
Allows you to override the background-image property of the highlight element, enabling you to fully customize the gradient. See example below.
| undefined |
There are two ways to wrap a skeleton in a container:
function Box({ children }: PropsWithChildren<unknown>) {
return (
<div
style={{
border: '1px solid #ccc',
display: 'block',
lineHeight: 2,
padding: '1rem',
marginBottom: '0.5rem',
width: 100,
}}
>
{children}
</div>
);
}
// Method 1: Use the wrapper prop
const wrapped1 = <Skeleton wrapper={Box} count={5} />;
// Method 2: Do it "the normal way"
const wrapped2 = (
<Box>
<Skeleton />
</Box>
);
You may want to make the gradient used in the highlight element narrower or wider. To do this, you can set the customHighlightBackground prop. Here's an example of a narrow highlight:
<Skeleton customHighlightBackground="linear-gradient(90deg, var(--base-color) 40%, var(--highlight-color) 50%, var(--base-color) 60%)" />
If you use this prop, the baseColor and highlightColor props are ignored, but you can still reference their corresponding CSS variables as shown in the above example.

display: flex!In the example below, the width of the skeleton will be 0:
<div style={{ display: 'flex' }}>
<Skeleton />
</div>
This happens because the skeleton has no intrinsic width. You can fix it by
applying flex: 1 to the skeleton container via the containerClassName prop.
For example, if you are using Tailwind, your code would look like this:
<div style={{ display: 'flex' }}>
<Skeleton containerClassName="flex-1" />
</div>
In the example below, the height of the <div> will be slightly larger than 30
even though the react-loading-skeleton element is exactly 30px.
<div>
<Skeleton height={30} />
</div>
This is a consequence of how line-height works in CSS. If you need the <div>
to be exactly 30px tall, set its line-height to 1. See
here
for more details.
Contributions are welcome! See CONTRIBUTING.md to get started.
Our logo is based off an image from Font Awesome. Thanks!