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.
Setting Up the Project
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.
Creating the Shimmer Effect
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.
Styling the Loader
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; `;
Structuring the Skeleton
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.
Main Wrapper and SpaceWrapper
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; `;
Final Component
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:
Conclusion
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!
Add comment