Andy from Webcrunch

Subscribe for email updates:

Portrait of Andy Leverenz
Andy Leverenz

November 1, 2018

Last updated November 5, 2023

How to Create a Skeleton Screen Loading Effect

My Let's Build: With JavaScript series continues with a new installment where I teach you how to create a skeleton screen loading effect with vanilla JavaScript, HTML, and CSS.

This tutorial is inspired by a recent trend I've seen emerging on both my employer's platform (Dribbble), as well as many popular products we all know and love. Google images, for instance, utilizes this feature when you perform a search but they go one step further and dynamically determine the color of the image to be loaded.

The effect known commonly as the skeleton screen effect is a combination of CSS and JavaScript of which combine produces perceived loading states for components on a page. A content card, for example, might have a placeholder for an image, title, and an excerpt that will display on the page's initial load. Once the content gets injected into the DOM we finally see the content that a user came for in the first place.

View the CodePen

Why use this technique?

In my own perspective, I find it a better-suited solution than traditional CSS spinners or image loaders as the user can predict what kind of content they are about to see. Styling placeholders combined with animations to indicate things are happening is a really great way to tell a user to hang on for a quick second.

How does it all work?

For this tutorial, I'll start by saying I'm faking the loading time using JavaScript built-in setTimeout function. With that established, I can go on to say we'll be using some basic HTML to create a card component. Our SCSS will allow us to both create the card UI as well as fire the animation of which we will display until the main content we want gets rendered.


I start with a container div that wraps our card component and renders it in the center of the page. Then I create a new section element that contains a figure element and a card-detail element. Within the card-detail element is an h3 and p tag. All of our markup is empty because I'll be injecting content using JavaScript. There are better ways to approach this from the stance of SEO and Accessibility but I'll ignore those for now (I know, I shouldn't!).

Notice the addition of a loading class on three of the elements. The CSS will target these and create a background image/color scenario that is animated. This styling gives the perception of a loading state.

<div class="container">
  <section class="card">
    <figure class="card-image loading"></figure>
    <div class="card-detail">
      <h3 class="card-title loading"></h3>
      <p class="card-description loading"></p>


The card can't look like a card without some CSS so below is the styles I used to get the look and feel down. Pay attention to the .loading class. There's where all the magic happens for the skeleton screen effect. We use a gradient with a unique setting. From there we animate it infinitly.

$border-color: #dae1e7;
$background-color: #f4f4f4;
$text-color: #333;

body {
  background: $background-color;
  margin: 0;
  padding: 0;
  font-family: system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue;
  color: $text-color;

.container {
  margin: 2rem auto;
  max-width: 800px;
  display: flex;
  justify-content: center;

.card {
  overflow: hidden;
  background: white;
  border-radius: .25rem;
  max-width: 380px;
  width: 380px;
    0 15px 30px 0 rgba(0,0,0,0.05),
    0 5px 15px 0 rgba(0,0,0,.05);
  transition: ease box-shadow 0.3s;
  &:hover {
    0 15px 60px 0 rgba(0,0,0,0.08),
    0 5px 25px 0 rgba(0,0,0,.08);

.card-detail {
  padding: .5rem 1rem;

  h3 { 
    font-size: 1.5rem; 
    margin-bottom: none; 
    line-height: .09;

  p {
    line-height: 1.3rem;  

.card-image {
  margin: 0;
  padding: 0;
  height: 200px;
  overflow: hidden;

  img {
    max-width: 100%;
    height: auto;

.loading {
  position: relative;
  background-color: #E2E2E2;

  &.card-image {
    border-radius: 0;

  &::after {
    display: block;
    content: '';
    position: absolute;
    width: 100%;
    height: 100%;
    transform: translateX(-100%);
    background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .2), transparent);
    animation: loading 1.5s infinite;

@keyframes loading {
  100% {
    transform: translateX(100%);

.card-title.loading {
  height: 1.8rem;

.card-image.image {
  max-width: 100%;
  height: auto;

.card-description.loading {
  height: 80px;

The JavaScript

The JavaScript here is fairly straight-forward. We built up a function to fire things all at once called renderCard. Within that function, we set the content of the elements we want to apply our skeleton effect to. On top of that, I created a new function that gets referenced here.

The other function hooks into the JavaScript API to dynamically create a new img element within the figure element on our HTML. From there I append attributes that allow the image to display the way we want.

const cardImage = document.querySelector('.card-image');
const cardTitle = document.querySelector('.card-title');
const cardDesc = document.querySelector('.card-description');

const renderCard = () => {
  cardTitle.textContent = 'Card Title Yo!';  
  cardDesc.textContent = 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Vero dicta repellat quibusdam assumenda at distinctio similique eos? Officiis voluptatem accusamus, id odit, quos eaque nemo, dicta quidem eum eligendi veritatis.';

function createImage() {
  const img = new Image();
  img.setAttribute('alt', 'A cloud day at a mountain base');
  img.setAttribute('src', '');

setTimeout(() => {
}, 5000);

// window.addEventListener('load', () => {
//   renderCard();
// });

Where to go from here?

The skeleton screen loading technique is a great one. This example is fairly primitive and rather than setting content with JavaScript you might be pulling it from a database, some JSON, or some other scenario that gets content on the screen. That all deals with Ajax, the fetch API, or something similar to get the data back (at least using JavaScript only). I plan to dive deeper into more of those concepts in upcoming videos so do stay tuned.

The Series So Far

Link this article
Est. reading time: 6 minutes
Stats: 120,911 views



Part of the Let's Build: With JavaScript collection