DEV Community

Cover image for Build a Pixel Perfect Skeleton Loader Using CSS 🚀
Ram Maheshwari ♾️
Ram Maheshwari ♾️

Posted on • Updated on

Build a Pixel Perfect Skeleton Loader Using CSS 🚀

Skeleton Loaders are used very commonly in Modern Websites/Apps to indicate loading of data instead of using the traditional loaders, spinners, etc. which are boring and can lead to Poor User Experience 😵‍💫


I created this tutorial to share my knowledge of how to create a Perfect Skeleton Screen that looks like the exact replica of the original element 😉


We are going to convert the Food Blog Card to it's own Skeleton loader as shown in the GIF below 🖼️

Skeleton Loader Preview Demo


There are 3 Steps to creating a perfect Skeleton Screen 🤘


Step 1:

Make sure you already have implemented the HTML and CSS for the Original Element. In our case, I have included the code for the Food Blog Card below.

HTML Code ⬇️

<!DOCTYPE html>
<html lang="en">
  <head>
    <link
      href="https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@400;600;700;900&display=swap"
      rel="stylesheet"
    />
  </head>

  <body>
    <div class="container">
      <div class="card">
        <div class="img-cont">
          <img
            class="img"
            src="https://images.unsplash.com/photo-1594398028856-f253a046f417?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1189&q=80"
            alt="Food image"
          />
        </div>
        <div class="user-info">
          <div class="user-avatar-cont">
            <img
              src="https://i.ibb.co/JzNYHV9/image-1.jpg"
              alt="User Image"
              class="user-avatar-img"
            />
          </div>
          <div class="user-details">
            <div class="user-name"><span>Natalia Rodrigues</span></div>
            <div class="user-profession"><span>Food Blogger</span></div>
          </div>
        </div>
      </div>
    </div>
  </body>
</html>

Enter fullscreen mode Exit fullscreen mode

CSS Code ⬇️

      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }

      html {
        font-size: 62.5%;
      }

      body {
        font-family: 'Source Sans Pro', sans-serif;
        display: flex;
        justify-content: center;
        align-items: center;
        min-height: 100vh;
        background: #eee;
      }

      .card {
        background: #fff;
        position: relative;
        padding: 2rem;
        border-radius: 5px;
        box-shadow: 0 10px 100px rgba(0, 0, 0, 0.1);
        width: 45rem;
        overflow: hidden;
      }

      .img-cont {
        height: 30rem;
        margin-bottom: 2rem;
        border-radius: 5px;
        overflow: hidden;
      }

      .img {
        width: 100%;
        height: 100%;
        object-fit: cover;
      }

      .user-info {
        display: flex;
        align-items: center;
      }

      .user-avatar-cont {
        width: 6rem;
        height: 6rem;
        margin-right: 2rem;
        border-radius: 50rem;
        overflow: hidden;
      }

      .user-avatar-img {
        width: 100%;
        height: 100%;
        object-fit: cover;
      }

      .user-details {
        flex-grow: 1;
        display: flex;
        flex-direction: column;
        justify-content: space-between;
      }

      .user-name {
        font-size: 2.2rem;
        margin-bottom: 5px;
        color: #444;
        text-transform: uppercase;
        letter-spacing: 1px;
        font-weight: 600;
      }

      .user-profession {
        font-size: 1.3rem;
        color: #999;
        border-radius: 2px;
        text-transform: uppercase;
        letter-spacing: 1px;
        font-weight: 600;
      }
Enter fullscreen mode Exit fullscreen mode

Result ⬇️

Food Blog Result Demo


Step 2:

Now we have to convert the card into its own Skeleton Version. To do that, I will add an extra class called card--skeleton on the HTML div with the class called card as shown below so we can target the elements inside the card.

      <div class="card card--skeleton">
Enter fullscreen mode Exit fullscreen mode

Now, we have 2 Images inside the card, the 1st image is the image of the Pizza and the 2nd Image is the image of the Person. Both of these images are wrapped inside their own container and those containers have their specific height.

Now, we will remove both of these images.

    <div class="card">
      <div class="img-cont">
        <!-- Removed Image -->
      </div>
      <div class="user-info">
        <div class="user-avatar-cont">
          <!-- Removed Image -->
        </div>
        <div class="user-details">
          <div class="user-name"><span>Natalia Rodrigues</span></div>
          <div class="user-profession"><span>Food Blogger</span></div>
        </div>
      </div>
    </div>
Enter fullscreen mode Exit fullscreen mode

And we will add a background color to the image containers as shown below using the skeleton class.

.card--skeleton .img-cont {
  background: #eee;
}

.card--skeleton .user-avatar-cont {
  background: #eee;
}
Enter fullscreen mode Exit fullscreen mode

So the end result will look like this ⬇️

Skeleton Loader Preview Demo

We will do the same thing with User Name and User Profession elements. We will change the background color of both of these elements along with the text color inside it. The background color and the text color will be the same.
I have also added some border-radius which is optional.

.card--skeleton .user-name span,
.card--skeleton .user-profession span {
  background: #eee;
  color: #eee;
  border-radius: 5px;
}
Enter fullscreen mode Exit fullscreen mode

Now, the end result will look like this ⬇️

Skeleton Loader Preview Demo

Looking cool, right? 😉

So, now we can move on to the 3rd Step where we will add the shining effect animation ⚡


Step 3:

In this step, we will add the Shining Effect Animation on the entire Skeleton Card.

To implement that, we will target the before sudo class of card--skeleton as shown below.

      .card--skeleton::before {
        content: '';
        position: absolute;
        background: linear-gradient(
          90deg,
          transparent,
          rgba(255, 255, 255, 0.9),
          transparent
        );
        width: 50%;
        height: 100%;
        top: 0;
        left: 0;
      }
Enter fullscreen mode Exit fullscreen mode

So, we have added the before sudo element which is absolutely positioned and has the same height as the card--skeleton and has 50% width of the class--skeleton.

We also added linear-gradient as the background with 3 colors ( transparent color, white color, transparent color ) in the right direction.

So, it will make our card--skeleton look like this ⬇️

Skeleton Loader Preview Demo

Now we will use CSS keyframes to move the gradient from the left side to the right side. Inside keyframes, we will target the transform property to skew ( basically to turn the linear gradient from | to / ) the element and to translate it in the X-direction.

I have already added the overflow: hidden value to the card element so when the before element goes outside of the card boundaries because of the keyframes transform, it's not going to be visible outside of the boundaries of card which is what we need.

      @keyframes loading {
        0% {
          transform: skewX(-10deg) translateX(-100%);
        }
        100% {
          transform: skewX(-10deg) translateX(200%);
        }
      }

      .card--skeleton::before {
        ...
        /* Refer the Keyframe inside the Animation */
        animation: loading 0.6s infinite;
      }

Enter fullscreen mode Exit fullscreen mode

So, the end result will finally look like this ⬇️

Skeleton Loader Preview Demo

and that's what we wanted 🥳🤘
I hope you find this post to be helpful and thanks for reading it 😇


Important 😸

I regularly post useful content related to Web Development and Programming on Linkedin and Twitter. You should consider Connecting with me or Following me on these Platforms.

Linkedin Profile, Twitter Profile


P.S. ✌️

I recently created one OpenSource Project which is a Portfolio Website Template for Developers called Dopefolio and wrote an Article about it on dev.to.

Feel free to check the article here 😄


Please React with ❤️ + 🦄 + 🔖 , As it takes time to create such content so it will be very helpful if you show some love to this post.

Share your feedback by Commenting below 💬

Drop me a Follow for more Awesome content related to Web Development and Programming 🙌

Thank you for your support ❤️

Top comments (30)

Collapse
 
tomyo profile image
Tomas Hayes • Edited

How do the user detail elements get the width, when on skeleton mode (without text inside)?

Nice idea to set dimensions on containers for this effect!

Collapse
 
rammcodes profile image
Ram Maheshwari ♾️

Yeah, if there's no text inside then we can set height for the elements. Thanks for sharing it, Tomas 😄

Collapse
 
modelhusband01 profile image
Model Husband 👑

How can I just use the avatar and text beside it alone

Thread Thread
 
rammcodes profile image
Ram Maheshwari ♾️

If you mean how I'm making the avatar and text sit on the same line then for that I'm using the flex property on the container which will make the items inside it like text and avatar sit on the same line.

Thread Thread
 
modelhusband01 profile image
Model Husband 👑

Okay what if I want to use the avatar and text repeatedly just like messages in Whatsapp how will I do that

Collapse
 
amircahyadi profile image
Amir-cahyadi

Its awesome 👍

Collapse
 
rammcodes profile image
Ram Maheshwari ♾️

Thanks a lot, @amircahyadi 😄

Collapse
 
midouwebdev profile image
MidouWebDev

Awesome !

Collapse
 
rammcodes profile image
Ram Maheshwari ♾️

Thanks a lot, @midouwebdev 😄

Collapse
 
plondon profile image
Philip London

I've re-implemented this as a lesson on codeamigo.dev. You can check it out here: codeamigo.dev/lessons/start/87 if you're interested!

Collapse
 
rammcodes profile image
Ram Maheshwari ♾️

Amazing, Thank you so much ❤️

Collapse
 
yaxx profile image
yaxx

Nice one, but how do you determine the number of skeletons to display compare to the number of incoming data

Collapse
 
rammcodes profile image
Ram Maheshwari ♾️

It's up to you on how many Skeletons you wanna display and if you have pagination like you only show n number of items then you can show n number of skeleton screens.

Collapse
 
yaxx profile image
yaxx

I will personally render the skeletons just enough to fill the items view port even though the number of incoming items are greater, less than or equal to the number of pre-rendered skeletons.

Thread Thread
 
rammcodes profile image
Ram Maheshwari ♾️

That's a great idea, I will keep that in mind and hopefully will implement enough skeletons to fit the viewport/layout. Thanks a lot for sharing 😄

Collapse
 
zeonlife profile image
ZeonLight

nice

Collapse
 
rammcodes profile image
Ram Maheshwari ♾️

Thanks a lot, @zeonlife

Collapse
 
maitrungdong profile image
Mai Trung Đông • Edited

An amazing good job, bro!!!! ❤💯

Collapse
 
rammcodes profile image
Ram Maheshwari ♾️

Thank you so much, @maitrungdong 😄

Collapse
 
sirmed profile image
Sirmed Mehmood

But how de we make the "shine" dissapear when the content is loaded?

Collapse
 
rammcodes profile image
Ram Maheshwari ♾️ • Edited

The idea here is to keep the Skeleton loader as its own separate component and card ( where we will show the data ) as its own component. So, you don't need to use the skeleton card to show the loaded data, we will show that in the card component and we will only use the skeleton loader element/component when we request the data to the server and until the request returns the data, once the data is returned back from the server, then we will use the original card to display the data. Hope this helps.

Collapse
 
alanrmachado profile image
AlanRmachado

Amazing!!

Collapse
 
rammcodes profile image
Ram Maheshwari ♾️ • Edited

Thanks a lot, @alanrmachado 😄

Collapse
 
jedstroke profile image
Jedidiah Gabriel

Well explained

Collapse
 
rammcodes profile image
Ram Maheshwari ♾️

Thanks for sharing your feedback, @jedstroke 😄