Using Skeletons Instead of Loading Spinner

post image

Most app nowdays need to fetch data externally and since this might take a while we should show in the meantime some form of UI to tell the user the app will load soon. The easiest solution for this is to show the famous Loading Spinner.

While this may be fast and easy solution it presents several UX problems. When a User sees a loading spinner he feels like he needs to wait and when the user sees more of them across the app he might get the feeling the app is slow. Another problem might be is when the data finally loads there might be a layout shift in the UI.

So one solution to the problem is using a skeleton.

skeleton window

Not this one… 😊

A skeleton is component that displays a placeholder preview of your content before data gets loaded. This way the user can process what the page is gonna look like while waiting for the data.

Let’s see an Example. We have a User Profile Card that looks like this, all the data need to be loaded from server:

avatar
My name is Rami
I love ice cream and playing golf

UserProfileCard.tsx
function UserProfileCard({ imageSrc, title, description }) {
  return (
    <div className={styles.container}>
      <img className={styles.avatar} src={imageSrc} alt="avatar" />
      <div className={styles.texts}>
        <div className={styles.title}>{title}</div>
        <div className={styles.description}>{description}</div>
      </div>
    </div>
  );
}

A skeleton will try to mimic the original component structure so we can create another component with similiar structure to the real.

To give more feeling of loading effect/something is gonna happen we can add animation for the color:


UserProfileSkeleton.tsx
import clsx from "clsx";
import styles from "./userProfileSkeleton.module.scss";
 
export default function UserProfileSkeleton() {
  return (
    <div className={styles.container}>
      <div className={clsx(styles.avatar, styles.shimmer)} />
      <div className={styles.texts}>
        <div className={clsx(styles.title, styles.shimmer)} />
        <div className={clsx(styles.description, styles.shimmer)} />
      </div>
    </div>
  );
}

userProfileSkeleton.module.scss
.container {
  background-color: gray;
  padding: 8px 16px;
  display: flex;
  gap: 24px;
  align-items: center;
  width: 100%;
  max-width: 400px;
  border-radius: 4px;
}
 
.avatar {
  height: 64px;
  width: 64px;
  border-radius: 50%;
  background-color: #424242;
}
 
.texts {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
 
.title {
  line-height: 18px;
  background-color: #424242;
  width: 160px;
  border-radius: 2px;
  height: 27px;
}
 
.description {
  line-height: 14px;
  background-color: #424242;
  width: 200px;
  border-radius: 2px;
  height: 21px;
}
 
.shimmer {
  position: relative;
  overflow: hidden;
 
  &::after {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    transform: translateX(-100%);
    background: linear-gradient(
      90deg,
      transparent,
      rgba(0, 0, 0, 0.15),
      transparent
    );
    animation: shimmer 2s infinite;
    content: "";
  }
}
 
@keyframes shimmer {
  100% {
    transform: translateX(100%);
  }
}

And finally we can add a logic to conditinaly render the the skeleton if data and loading.

avatar
Alfonso Roberto
I love ice cream and playing golf

And there we have it a simple skeleton for user profile card.

One thing to keep in mind is it no always easy to predict how the real data is gonna show up so in this scenario try to shape the skeleton as close as you can to the real component to prevent big layout shifts. As you can see building skeleton requires a bit more work than a loader, but it’s definitely delivers a better user experience than a loading spinner.