Hello Sunil
custom-skeleton-loader-react-feature-image

Building a Custom Skeleton Loader in React using Styled-Components

In this tutorial, we’ll explore how to create a customizable skeleton loader in React using styled-components. Skeleton loaders are used to indicate the loading state of a UI element, offering a smooth placeholder before the actual data is loaded.

We will implement a Skeleton Loader component that mimics the structure of your content and applies a shimmering effect to enhance the user experience during data loading. Let’s dive into the code and walk through the process.

To get started, ensure you have a React app set up. If not, create one using:

npx create-next-app@latest
npm install styled-components

Next, create a new file named SkeletonLoader.js in the components folder.

We need a keyframes animation that defines the shimmer effect. This will create a moving background gradient that gives the illusion of content loading.

// Keyframes for the shimmer effect
const placeHolderShimmer = keyframes`
  0% {
    transform: translateZ(0);
    background-position: -468px 0;
  }
  100% {
    transform: translateZ(0);
    background-position: 468px 0;
  }
`;

This animation moves a background gradient from left to right, simulating a loading state.

Next, we’ll define the LoaderWrapper component using styled-components to create our skeleton loader. This component is fully customizable using props for width, height, border radius, and margin.

const LoaderWrapper = styled.div`
  width: ${(props) => props.width}px;
  height: ${(props) => props.height}px;
  margin-top: ${(props) => (props.margintop ? props.margintop : 0)}px;
  margin-left: ${(props) => (props.marginleft ? props.marginleft : 0)}px;
  border-radius: ${(props) => (props.borderRadius ? props.borderRadius : 3)}%;
  background: #e6e6e6;
  background: linear-gradient(90deg, #eee 8%, #ddd 18%, #eee 33%);
  background-size: 800px 104px;
  position: relative;
  margin-bottom: 10px;
  animation: ${placeHolderShimmer} 3s linear infinite forwards;
  will-change: transform;
  -webkit-backface-visibility: hidden;
`;

We’ll now build the skeleton loader that mimics the structure of the real content. The SkeletonLoader component will create multiple LoaderWrapper components with varying sizes to represent different sections of the page.

const SkeletonLoader = ({ width }) => {
  return (
    <div>
      <MainWrapper>
        <SpaceWrapper>
          <LoaderWrapper
            width={width || 700}
            height={20}
            borderRadius={3}
          ></LoaderWrapper>
          <LoaderWrapper
            width={width || 650}
            height={15}
            borderRadius={3}
          ></LoaderWrapper>
          <LoaderWrapper
            width={width || 450}
            height={15}
            borderRadius={3}
          ></LoaderWrapper>
          <LoaderWrapper
            width={width || 350}
            height={15}
            borderRadius={3}
          ></LoaderWrapper>
        </SpaceWrapper>

        <SpaceWrapper>
          <div style={{ display: "flex", gap: "40px" }}>
            <LoaderWrapper
              width={width || 100}
              height={100}
              borderRadius={50}
            ></LoaderWrapper>

            <LoaderWrapper
              width={width || 100}
              height={100}
              borderRadius={50}
            ></LoaderWrapper>

            <LoaderWrapper
              width={width || 100}
              height={100}
              borderRadius={50}
            ></LoaderWrapper>

            <LoaderWrapper
              width={width || 100}
              height={100}
              borderRadius={50}
            ></LoaderWrapper>
            <LoaderWrapper
              width={width || 100}
              height={100}
              borderRadius={50}
            ></LoaderWrapper>
          </div>
        </SpaceWrapper>

        <SpaceWrapper>
          <LoaderWrapper
            width={width || 700}
            height={20}
            borderRadius={3}
          ></LoaderWrapper>
          <LoaderWrapper
            width={width || 650}
            height={15}
            borderRadius={3}
          ></LoaderWrapper>
          <LoaderWrapper
            width={width || 450}
            height={15}
            borderRadius={3}
          ></LoaderWrapper>
          <LoaderWrapper
            width={width || 350}
            height={15}
            borderRadius={3}
          ></LoaderWrapper>
        </SpaceWrapper>

        <SpaceWrapper>
          <LoaderWrapper
            width={width || 700}
            height={20}
            borderRadius={3}
          ></LoaderWrapper>
          <LoaderWrapper
            width={width || 650}
            height={15}
            borderRadius={3}
          ></LoaderWrapper>
          <LoaderWrapper
            width={width || 450}
            height={15}
            borderRadius={3}
          ></LoaderWrapper>
          <LoaderWrapper
            width={width || 350}
            height={15}
            borderRadius={3}
          ></LoaderWrapper>
        </SpaceWrapper>

        <SpaceWrapper>
          <LoaderWrapper
            width={width || 700}
            height={20}
            borderRadius={3}
          ></LoaderWrapper>
          <LoaderWrapper
            width={width || 650}
            height={15}
            borderRadius={3}
          ></LoaderWrapper>
          <LoaderWrapper
            width={width || 450}
            height={15}
            borderRadius={3}
          ></LoaderWrapper>
          <LoaderWrapper
            width={width || 350}
            height={15}
            borderRadius={3}
          ></LoaderWrapper>
        </SpaceWrapper>

        <SpaceWrapper>
          <LoaderWrapper
            width={width || 700}
            height={20}
            borderRadius={3}
          ></LoaderWrapper>
          <LoaderWrapper
            width={width || 650}
            height={15}
            borderRadius={3}
          ></LoaderWrapper>
          <LoaderWrapper
            width={width || 450}
            height={15}
            borderRadius={3}
          ></LoaderWrapper>
          <LoaderWrapper
            width={width || 350}
            height={15}
            borderRadius={3}
          ></LoaderWrapper>
        </SpaceWrapper>

        <SpaceWrapper>
          <div style={{ display: "flex", gap: "40px" }}>
            <LoaderWrapper
              width={width || 100}
              height={100}
              borderRadius={3}
            ></LoaderWrapper>

            <LoaderWrapper
              width={width || 100}
              height={100}
              borderRadius={3}
            ></LoaderWrapper>

            <LoaderWrapper
              width={width || 100}
              height={100}
              borderRadius={3}
            ></LoaderWrapper>

            <LoaderWrapper
              width={width || 100}
              height={100}
              borderRadius={3}
            ></LoaderWrapper>
            <LoaderWrapper
              width={width || 100}
              height={100}
              borderRadius={3}
            ></LoaderWrapper>
          </div>
        </SpaceWrapper>
      </MainWrapper>
    </div>
  );
};

In this component, we are simulating both rectangular loaders (for text) and circular loaders (for profile images or icons). The structure is broken into sections using SpaceWrapper for consistent spacing.

We wrap our loaders inside the MainWrapper to center it on the page and SpaceWrapper to add margins between each section.

const MainWrapper = styled.div`
  width: 700px;
  margin: 0 auto;
`;

const SpaceWrapper = styled.div`
  margin-top: 30px;
  margin-bottom: 50px;
`;

Here’s the full code for the SkeletonLoader.js:

SkeletonLoader.js

//SkeletonLoader.js
"use client";


// Import Libraries
import React from "react";
import styled, { keyframes } from "styled-components";

// Keyframes for the shimmer effect
const placeHolderShimmer = keyframes`
  0% {
    transform: translateZ(0);
    background-position: -468px 0;
  }
  100% {
    transform: translateZ(0);
    background-position: 468px 0;
  }
`;

// CSS-in-JS
const MainWrapper = styled.div`
  width: 700px;
  margin: 0 auto;
`;
const LoaderWrapper = styled.div`
  width: ${(props) => props.width}px;
  height: ${(props) => props.height}px;
  margin-top: ${(props) => (props.margintop ? props.margintop : 0)}px;
  margin-left: ${(props) => (props.marginleft ? props.marginleft : 0)}px;
  border-radius: ${(props) => (props.borderRadius ? props.borderRadius : 3)}%;
  background: #e6e6e6;
  background: linear-gradient(90deg, #eee 8%, #ddd 18%, #eee 33%);
  background-size: 800px 104px;
  position: relative;
  margin-bottom: 10px;
  animation: ${placeHolderShimmer} 3s linear infinite forwards;
  will-change: transform;
  -webkit-backface-visibility: hidden;
`;
const SpaceWrapper = styled.div`
  margin-top: 30px;
  margin-bottom: 50px;
`;

const SkeletonLoader = ({ width }) => {
  return (
    <div>
      <MainWrapper>
        <SpaceWrapper>
          <LoaderWrapper
            width={width || 700}
            height={20}
            borderRadius={3}
          ></LoaderWrapper>
          <LoaderWrapper
            width={width || 650}
            height={15}
            borderRadius={3}
          ></LoaderWrapper>
          <LoaderWrapper
            width={width || 450}
            height={15}
            borderRadius={3}
          ></LoaderWrapper>
          <LoaderWrapper
            width={width || 350}
            height={15}
            borderRadius={3}
          ></LoaderWrapper>
        </SpaceWrapper>

        <SpaceWrapper>
          <div style={{ display: "flex", gap: "40px" }}>
            <LoaderWrapper
              width={width || 100}
              height={100}
              borderRadius={50}
            ></LoaderWrapper>

            <LoaderWrapper
              width={width || 100}
              height={100}
              borderRadius={50}
            ></LoaderWrapper>

            <LoaderWrapper
              width={width || 100}
              height={100}
              borderRadius={50}
            ></LoaderWrapper>

            <LoaderWrapper
              width={width || 100}
              height={100}
              borderRadius={50}
            ></LoaderWrapper>
            <LoaderWrapper
              width={width || 100}
              height={100}
              borderRadius={50}
            ></LoaderWrapper>
          </div>
        </SpaceWrapper>

        <SpaceWrapper>
          <LoaderWrapper
            width={width || 700}
            height={20}
            borderRadius={3}
          ></LoaderWrapper>
          <LoaderWrapper
            width={width || 650}
            height={15}
            borderRadius={3}
          ></LoaderWrapper>
          <LoaderWrapper
            width={width || 450}
            height={15}
            borderRadius={3}
          ></LoaderWrapper>
          <LoaderWrapper
            width={width || 350}
            height={15}
            borderRadius={3}
          ></LoaderWrapper>
        </SpaceWrapper>

        <SpaceWrapper>
          <LoaderWrapper
            width={width || 700}
            height={20}
            borderRadius={3}
          ></LoaderWrapper>
          <LoaderWrapper
            width={width || 650}
            height={15}
            borderRadius={3}
          ></LoaderWrapper>
          <LoaderWrapper
            width={width || 450}
            height={15}
            borderRadius={3}
          ></LoaderWrapper>
          <LoaderWrapper
            width={width || 350}
            height={15}
            borderRadius={3}
          ></LoaderWrapper>
        </SpaceWrapper>

        <SpaceWrapper>
          <LoaderWrapper
            width={width || 700}
            height={20}
            borderRadius={3}
          ></LoaderWrapper>
          <LoaderWrapper
            width={width || 650}
            height={15}
            borderRadius={3}
          ></LoaderWrapper>
          <LoaderWrapper
            width={width || 450}
            height={15}
            borderRadius={3}
          ></LoaderWrapper>
          <LoaderWrapper
            width={width || 350}
            height={15}
            borderRadius={3}
          ></LoaderWrapper>
        </SpaceWrapper>

        <SpaceWrapper>
          <LoaderWrapper
            width={width || 700}
            height={20}
            borderRadius={3}
          ></LoaderWrapper>
          <LoaderWrapper
            width={width || 650}
            height={15}
            borderRadius={3}
          ></LoaderWrapper>
          <LoaderWrapper
            width={width || 450}
            height={15}
            borderRadius={3}
          ></LoaderWrapper>
          <LoaderWrapper
            width={width || 350}
            height={15}
            borderRadius={3}
          ></LoaderWrapper>
        </SpaceWrapper>

        <SpaceWrapper>
          <div style={{ display: "flex", gap: "40px" }}>
            <LoaderWrapper
              width={width || 100}
              height={100}
              borderRadius={3}
            ></LoaderWrapper>

            <LoaderWrapper
              width={width || 100}
              height={100}
              borderRadius={3}
            ></LoaderWrapper>

            <LoaderWrapper
              width={width || 100}
              height={100}
              borderRadius={3}
            ></LoaderWrapper>

            <LoaderWrapper
              width={width || 100}
              height={100}
              borderRadius={3}
            ></LoaderWrapper>
            <LoaderWrapper
              width={width || 100}
              height={100}
              borderRadius={3}
            ></LoaderWrapper>
          </div>
        </SpaceWrapper>
      </MainWrapper>
    </div>
  );
};

export default SkeletonLoader;

Output:

The skeleton loader we’ve created can easily be customized for different layouts by adjusting the width, height, and border radius of each LoaderWrapper. It provides a smooth shimmer effect while content is loading and keeps the UI engaging for users.

Feel free to expand this further by adding more complex shapes or loaders for different types of content!

Happy coding!

How useful was this post?

Click on a star to rate it!

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.

We are sorry that this post was not useful for you!

Let us improve this post!

Tell us how we can improve this post?

Similar articles you may like

Sunil Pradhan

Hi there 👋 I am a front-end developer passionate about cutting-edge, semantic, pixel-perfect design. Writing helps me to understand things better.

Add comment

Stay Updated

Want to be notified when our article is published? Enter your email address below to be the first to know.

Sunil Pradhan

Hi there 👋 I am a front-end developer passionate about cutting-edge, semantic, pixel-perfect design. Writing helps me to understand things better.