How to Create a React Skeleton Loader Component with TailwindCSS and NextJS
Jasser Mark Arioste
Hello, hustlers! In this tutorial, you'll learn how to create a dynamic react skeleton loader component using TailwindCSS.
Why use a skeleton loader? #
A skeleton loader offers the user a simple preview of the loading content and decreases the perceived load time. Avatars, cards, lists, tables, and content blocks are good candidates for using a skeleton loader.
When to use a skeleton loader? #
- On heavy content pages like dashboards, message apps, cards, etc.
- When there are multiple elements that require a loading indicator.
- On dynamically loaded components. For example, NextJS offers code splitting by using the
dynamic
import feature.
Final Output #
This is what we'll be making today:
Tutorial Objectives #
Here are our tutorial objectives, We're going to make 3 components:
- Make a dynamic
SkeletonLoader
component that has a pulse animation. It can accept children components and animate them. - Make a
SkeletonCircle
component - Make a
SkeletonRectangle
Component.
Step 1 - Project Setup #
First, we have to install tailwind as a dependency. If you're using NextJS, you may check my previous tutorial: How to Set Up NextJS and TailwindCSS in 2023. If you're using some other react framework you may check the tailwind documentation on how to install it.
Step 2 - Creating a Skeleton Loader component #
First, let's create the SkeletonLoader
component. This is just a wrapper component with a pulse animation. First, create the file components/SkeletonLoader.tsx
and copy the code below:
import { ComponentPropsWithRef } from "react"; // React 18 example // Use the div element props type DivProps = ComponentPropsWithRef<"div">; // Define the component. this is basically a div wrapper with a pulse animation for the skeleton loading const SkeletonLoader: React.FC<DivProps> = ({ children, className, ...props }) => { return ( <div className={["animate-pulse", className].join(" ")} {...props}> {children} </div> ); }; export default SkeletonLoader;
12345678910111213141516171819
To use this component, we can simply put any child elements there and it will create a basic skeleton loader. For example:
// pages/index.tsx import type { NextPage } from "next"; import SkeletonLoader from "../components/SkeletonLoader"; const Home: NextPage = () => { return ( <div className="container mx-auto"> <SkeletonLoader className="flex gap-2 my-2 w-80"> <div className="h-12 w-12 rounded-full bg-gray-200 flex-shrink-0"></div> <div className="w-full flex flex-col gap-2"> <div className="h-5 bg-gray-200"></div> <div className="h-5 w-1/2 bg-gray-200"></div> </div> </SkeletonLoader> </div> ); }; export default Home;
12345678910111213141516171819
The output will be like this:
Not bad at all. We're just defining the layout and how the children are set up. With TailwindCSS, we can use the utility classes to create a very flexible implementation. However, you'll also end up with a lot of code.
Most skeleton loaders have circle and rectangle components. That's what we'll be doing in the next steps.
Step 3 - Creating a SkeletonCircle
component
#
Let's create a skeleton circle component. We are able to specify the size
. But we still keep it very flexible by allowing us to override the className
prop. Create the file components/SkeletonCircle.tsx
:
// components/SkeletonCircle.tsx type Props = { className?: string; size: number; }; const SkeletonCircle = (props: Props) => { //use default styles but allow override const className = props.className ?? "rounded-full flex-shrink-0 bg-gray-200"; return ( <div className={className} style={{ height: props.size, width: props.size }} /> ); }; export default SkeletonCircle;
1234567891011121314151617
Let's refactor the previous example to use this component:
// pages/index.tsx import type { NextPage } from "next"; import SkeletonCircle from "../components/skeleton/SkeletonCircle"; import SkeletonLoader from "../components/skeleton/SkeletonLoader"; const Home: NextPage = () => { return ( <div className="container mx-auto"> <SkeletonLoader className="flex gap-2 my-10 w-80"> <SkeletonCircle size={48} /> <div className="w-full flex flex-col gap-2"> <div className="h-5 bg-gray-200"></div> <div className="h-5 w-1/2 bg-gray-200"></div> </div> </SkeletonLoader> </div> ); }; export default Home;
1234567891011121314151617181920
Step 4 - Creating a SkeletonRectangle
component
#
For the SkeletonRectangle
component, we want to be able to specify the height and width of each rectangle, how many rectangles to render, and the spacing/gap between each one.
First, create the file components/SkeletonRectangle.tsx
:
// components/SkeletonRectangle.tsx // Define the props type Props = { lines?: number; gap?: number; height?: number; className?: string; unEqualWidth?: boolean; }; // Add prop defaults const SkeletonRectangle: React.FC<Props> = ({ gap = 4, //4px lines = 1, height = 20, //20px className = "", unEqualWidth, }) => { const items = new Array(lines || 1).fill("x"); return ( <div className="w-full flex flex-col" style={{ rowGap: gap }}> {items.map((_, index) => { const len = items.length; const isLast = index === len - 1; const moreThanOne = len > 1; // use half-width if it's the last element const width = isLast && unEqualWidth && moreThanOne ? "w-1/2" : "w-full"; return ( <div key={index} style={{ height }} className={[width, className].join(" ")} /> ); })} </div> ); }; export default SkeletonRectangle;
123456789101112131415161718192021222324252627282930313233343536373839
Now, we have a very flexible SkeletonRectangle component that can accommodate pretty much any use case. For example, to use this component:
// pages/index.tsx import type { NextPage } from "next"; import SkeletonCircle from "../components/skeleton/SkeletonCircle"; import SkeletonLoader from "../components/skeleton/SkeletonLoader"; import SkeletonRectangle from "../components/skeleton/SkeletonRectangle"; const Home: NextPage = () => { return ( <div className="container mx-auto"> <SkeletonLoader className="flex gap-2 my-10 w-80"> <SkeletonCircle size={48} /> <SkeletonRectangle lines={3} unEqualWidth gap={8} className="bg-gray-200 rounded-md" /> </SkeletonLoader> <SkeletonLoader className="w-80"> <SkeletonRectangle height={100} className="bg-gray-200 rounded-md" /> </SkeletonLoader> </div> ); }; export default Home;
1234567891011121314151617181920212223242526
Here's the output:
Now you've got a pretty legit-looking SkeletonLoader
component!
Full Code and Demo #
The full code is available on GitHub: jmarioste/next-skeleton-loader-tutorial
The demo is available on Stackblitz: Next Skeleton Loader Tutorial
Conclusion #
We learned how to create a very customizable and reusable react skeleton loader component. TailwindCSS really helped us a lot by offering utility classes so we don't have to think much about styling and customization.
If you like this tutorial, please leave a like or share this article. For future tutorials like this, please subscribe to our newsletter or follow me on Twitter.
Credits: Image by Ales Krivec from Pixabay