<!-- Add or remove "loading" class on a parent to switch between the skeleton styles and theme styles -->

<button onClick="toggleLoading()" class="fixed">
  Toggle loading state
</button>

<!-- Generic card -->

<div class="card loading">
  <img class="loading__item" width="700" 
       loading="lazy" height="700" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" />
  <h2 class="loading__item"></h2>
  <p class="loading__item truncated"><br /><br /><br /></p>
  <button class="loading__item">Add to cart</button>
  <br /><br />
  <a class="loading__item" href="#">
    <small>
      Item details
    </small></a>
</div>

<div class="card example">
  <img loading="lazy" class="loading__item" width="700" height="700" loading="lazy" src="https://via.placeholder.com/700x700.jpg" />
  <h2 class="loading__item"><span>
      Title text</span></h2>
  <p class="loading__item truncated">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
  <button class="loading__item">Add to cart</button>
  <br /><br />
  <a class="loading__item" href="#">
    <small>
      Item details
    </small></a>
</div>

<!-- Generic card - variation -->

<div class="card loading center">
  <img loading="lazy" class="loading__item rounded" width="700" height="700" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" />
  <h2 class="loading__item"></h2>
  <p class="loading__item truncated test-bg-and-color"><br /><br /><br /></p>
  <button class="loading__item curved large">Add to cart</button>
  <br /><br />
  <a class="loading__item" href="#">
    <small>
      Item details
    </small></a>
</div>

<div class="card center example">
  <img class="loading__item rounded" width="700" height="700" src="https://via.placeholder.com/700x700.jpg" />
  <h2 class="loading__item">Title</h2>
  <p class="loading__item truncated test-bg-and-color">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
  <button class="loading__item curved large">Add to cart</button>
  <br /><br />
  <a class="loading__item" href="#">
    <small>
      Item details
    </small></a>
</div>

<!-- Form (label and input) -->

<form class="loading">
  <label class="loading__item"><small>Placeholder</small></label>
  <input class="loading__item" type="text" />
  <br /><br />
  <label class="loading__item test-bg-and-color"><small>Placeholder</small></label>
  <input class="loading__item test-bg-and-color" type="password" />
</form>

<form class="example">
  <label class="loading__item"><small>Username</small></label>
  <input class="loading__item" type="text" />
  <br /><br />
  <label class="loading__item test-bg-and-color"><small>Password</small></label>
  <input class="loading__item test-bg-and-color" type="password" />
</form>

<!-- Generic content block. We cannot assume what kind of content is loaded - just text, images, videos...  -->

<div class="loading">
  <div class="loading__item test-bg-and-color large">
    <br />
    <br />
    <br />
    <br />
    <br />
    <br />
    <br />
    <br />
    <br />
    <br />
    <br />
    <br />
    <br />
    <br />
    <br />
    <br />
    <br />
  </div>
</div>

<div class="example">
  <div class="loading__item test-bg-and-color large">
    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
  </div>
</div>

<script>
  function toggleLoading() {
    var examples = document.querySelectorAll(".example");
    for (var i = 0; i < examples.length; i++) {
      var current = examples[i];
      if (current.classList.contains("loading")) {
        current.classList.remove("loading");
      } else {
        current.classList.add("loading");
      }
    }
  }
</script>
:root {
  --color-skeleton-bg: #949494;
}

/* Play around with these values - test if the skeletons adapt to font-size and line-height */

body {
  font-size: 16px;
  line-height: 1.333;
}

/***********************************
 * Barebones skeleton styles start *
 ***********************************/

/* Universal skeleton loading styles should only affect the presentation, so all layout styles (dimensions, positioning) should be preserved. We need to make sure that the content is not visible and background & border styles are applied. In theory, these styles should be very robust and can be applied to most layout configs and content types. This skeleton loading styles is implemented in less than 15 lines of code. */

/* These classes are active only if parent has a "loading" class, so all skeleton styles can be easily turned off. */

/* NOTE: Image elements have a skeleton loading style applied to them while they're being (lazy) loaded. It's a simple background color, so when the image is loaded, skeleton loader is not visible.
*/

.loading .loading__item {
  /* Hide content and apply basic background and border-color styles. No animation is used, becuase it might look jarring in some cases and it doesn't add too much to UX. */
  background: var(--color-skeleton-bg);
  color: rgba(0, 0, 0, 0) !important;
  border-color: rgba(0, 0, 0, 0) !important;

  /* Turn off pointer and select events so placeholder text is not displayed */
  user-select: none;
  pointer-events: none;
}

/* Make sure all child elements are hidden, but preserve their dimensions and layout */

.loading .loading__item * {
  visibility: hidden;
}

/* Make sure that an element has at least a whitespace character as a child so it displays properly. This is useful when no text placeholder is present (element is empty). */

.loading .loading__item:empty::after,
.loading .loading__item *:empty::after {
  content: "\00a0";
}

/* Multi-row skeleton loaders can be added by adding <br/> elements. I assume that all children (content) elements get replaced with actual content once it's loaded. */

/*********************************
 * Barebones skeleton styles end *
 *********************************/

/* Demo styles - not required */

html {
  box-sizing: border-box;
}

*,
*::before,
*::after {
  box-sizing: inherit;
}

img {
  display: block;
  width: 100%;
  height: auto;
}

.truncated {
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
}

h2 {
  font-size: 2em;
  margin-top: 0.5em;
  margin-bottom: 0;
}

body {
  padding: 32px;
}

body {
  display: grid;
  grid-template-columns: repeat(2, 12.5em);
  grid-template-rows: auto;
  grid-gap: 48px 32px;
}

.center {
  text-align: center;
}

.curved {
  border-radius: 2rem;
}

.rounded {
  border-radius: 100%;
}

.large {
  padding: 0.5rem 1rem;
}

.test-bg-and-color {
  color: blue;
  background-color: orange;
  border: 2px solid blue;
}

label {
  display: inline-block;
  margin-bottom: 0.2em;
}

.fixed {
  position: fixed;
  top: 0;
  right: 0;
  padding: 0.5em 2em;
  font-weight: bold;
}

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.