Unable to load the content

Please attempt to reload the page or alternatively, try accessing it using a different browser.

Create a Skeleton Loading Profile Card using HTML CSS JavaScript

In this tutorial, we will create a stylish profile card with a skeleton loading effect using HTML, CSS and JavaScript. A skeleton loader is a placeholder that mimics the structure of the final content, providing users with a better experience while waiting for the actual data to load. 

By the end of this explanation, you will understand not only how to design this card but also the logic behind dynamically inserting content once it's ready.

Create-a-Skeleton-Loading-Profile-Card-using-HTML-CSS-JavaScript

Let me first explain what we’re going to build. Our profile card will have these components:

  • Cover Image: This is the background banner that will appear at the top of the card.
  • Avatar (Profile Picture): A circular image placed over the cover banner.
  • User Details: These include the user’s name, title and a short bio.
  • Social Media Icons: Links to popular social media platforms, styled with custom hover effects.
  • Skeleton Loading Effect: A shimmering animation that acts as a placeholder until the content is loaded dynamically using JavaScript.

This design not only looks professional but also enhances user interaction with subtle animations and responsiveness.

Also Read: Image folding Effect using HTML and CSS

Setting Up the HTML Structure

First, we start by writing the HTML for our profile card. Think of it as the skeleton that holds everything together.

  • The card itself will be a container divided into multiple sections: a cover, an avatar and the user details.
  • The social media icons will appear below the user details for additional functionality.

Inside the HTML structure, we’ll include placeholder elements with a class called skeleton. These elements represent the loading state before the actual content appears.

<div class="profile-card loading">
  <div class="cover-wrapper skeleton"></div>
  <div class="avatar-wrapper skeleton"></div>
  <div class="profile-details">
    <p class="user-name skeleton"></p>
    <p class="user-title skeleton"></p>
    <div class="separator"></div>
    <p class="user-about skeleton"></p>
  </div>
  <div class="social-handles-wrapper skeleton">
    <a href="#" class="icon bi bi-instagram"></a>
    <a href="#" class="icon bi bi-twitter-x"></a>
    <a href="#" class="icon bi bi-facebook"></a>
    <a href="#" class="icon bi bi-youtube"></a>
  </div>
</div>

Styling the Card with CSS

Now let’s focus on making the profile card visually appealing. Using CSS, we’ll define styles for each section of the card.

  • General Layout: We’ll use flexbox to center the card on the screen and ensure it adapts well to various screen sizes. The card will have rounded corners and a shadow for a clean, modern look.
  • Cover Wrapper: This section will contain the banner image. To make it visually interesting, we’ll use a clipped border effect, giving the banner a rounded edge at the bottom.
  • Avatar Wrapper: The profile picture will sit neatly in a circular container that slightly overlaps the cover image. We’ll add a white border around the avatar to distinguish it from the background.
  • User Details: The name, title and bio will be center-aligned. We’ll use custom fonts and colors to enhance readability and style.
  • Skeleton Loading Effect: This is where the magic happens! The skeleton placeholders will have a light gray color with a shimmering gradient animation. This gives users the impression that the content is actively loading.
@import url(https://fonts.googleapis.com/css2?family=Baloo+Bhai+2:wght@400&display=swap);

@import url(https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css);

body {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
  background: #ededed;
  min-width:320px;
  font-family: "Baloo Bhai 2";
}

.profile-card {
  max-width: 400px;
  background-color: #fff;
  width: 100%;
  display: grid;
  border-radius: 10px;
  overflow: hidden;
}

.skeleton {
  background-color: #e0e0e0;
  background-image: linear-gradient(90deg, #e0e0e0 0%, #cecece 50%, #e0e0e0 100%);
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite;
}

.cover-wrapper {
  height: 210px;
  position: relative;
  border-radius: 0 0 20% 20%;
  overflow: hidden;
}

.profile-card img {
  width: 100%;
  height: 100%;
}

.cover-wrapper img {
  object-fit: cover;
}

.avatar-wrapper {
  height: 150px;
  width: 150px;
  margin: auto;
  transform: translateY(-50%);
  border-radius: 50%;
  padding: 4px;
  background-color: white;
  box-shadow: 0px 0px 8px 0px #00000038;
  margin-bottom: -75px;
  overflow: hidden;
}

.avatar-wrapper img {
  object-fit: contain;
  border-radius: 50%;
}

.profile-details {
  text-align: center;
  line-height: 1.4;
}

.user-name {
  font-size: 22px;
  font-weight: bold;
  margin-top: 5px;
}

.user-name.skeleton {
  width: 30%;
  height: 25px;
  margin: 10px auto;
  border-radius: 5px;
}

.user-title {
  color: #e20909;
}

.user-title.skeleton {
  width: 50%;
  height: 15px;
  margin: 10px auto;
  border-radius: 5px;
}

.separator {
  background: linear-gradient(273deg, white, #215eae, white);
  width: 70%;
  height: 1px;
  margin: 10px auto 20px;
}

.loading a,
.loading .separator {
  visibility: hidden;
  opacity: 0;
}

.user-about {
  font-size: 14px;
  padding: 0 15px;
  margin-top: 10px;
}

.user-about.skeleton {
  width: 90%;
  height: 50px;
  margin: 10px auto;
  border-radius: 2px
}

.social-handles-wrapper {
  display: flex;
  gap: 10px;
  justify-content: center;
  margin: 15px 0;
}

.social-handles-wrapper.skeleton {
  width: 50%;
  height: 30px;
  margin: 10px auto;
  border-radius: 5px
}

.icon {
  color: white;
  width: 30px;
  height: 30px;
  text-align: center;
  align-content: center;
  border-radius: 5px;
  font-size: 15px;
}

.icon:hover {
  filter: brightness(0.9);
}

.icon::before {
  vertical-align: middle;
}

.bi-instagram {
  background: linear-gradient(45deg, #f09433 0%, #e6683c 25%, #dc2743 50%, #cc2366 75%, #bc1888 100%);
}

.bi-facebook {
  background: #215eae;
}

.bi-twitter-x {
  background: black;
}

.bi-youtube {
  background: #FF0000;
}

@keyframes shimmer {
  0% {
    background-position: 200% 0;
  }

  100% {
    background-position: -200% 0;
  }
}

Adding the Skeleton Loading Animation

For the skeleton effect, we’ll create a CSS animation that moves a gradient background across the placeholders. Let me break it down for you:

  • Base Style: The placeholders will have a solid background color (light gray) to simulate the shape of the final content.
  • Gradient Overlay: A linear gradient will add the shimmer effect, transitioning smoothly across the element.
  • Keyframes Animation: Using the @keyframes rule, we’ll define the movement of the gradient from left to right.

This effect is applied to all elements that have the skeleton class, such as the user’s name, title, bio and image containers.

Loading Dynamic Content with JavaScript

Now that we have the card’s structure and design ready, let’s bring it to life using JavaScript. The goal here is to simulate loading data from a server and dynamically insert it into the card.

const userData = {
  userName: "John Doe",
  userTitle: "Web Developer",
  userAbout: "Lorem ipsum dolor sit amet consectetu adipisicing elit. Fuga praesentium cupiditate hic, magnam at a.",
  profileUrl: "https://i.ibb.co/N7Kkmr1/profile.png",
  coverUrl: "https://i.ibb.co/Fmp3jvj/cover.jpg"
}
const userNameBox = document.querySelector(".user-name");
const userTitleBox = document.querySelector(".user-title");
const userAboutBox = document.querySelector(".user-about");
const coverWrapper = document.querySelector(".cover-wrapper");
const profileWrapper = document.querySelector(".avatar-wrapper");

const profileImg = document.createElement("img");
profileImg.src = userData.profileUrl;
const coverImg = document.createElement("img");
coverImg.src = userData.coverUrl

setTimeout(() => {
  insertUserDetails()
  document.querySelector(".profile-card").classList.remove("loading")
  removeSkeleton();
}, 3000);

const insertUserDetails = () => {
  userNameBox.innerHTML = userData.userName;
  userTitleBox.innerHTML = userData.userTitle;
  userAboutBox.innerHTML = userData.userAbout;
  coverWrapper.append(coverImg);
  profileWrapper.append(profileImg);
}

const removeSkeleton = () => {
  document.querySelectorAll(".skeleton").forEach((elem) => {
    elem.classList.remove("skeleton")
  })
}

Here’s how we do it:

  • Simulating a Server Response: We’ll create a userData object that contains the user’s name, title, bio and image URLs. This mimics the data you might receive from an API.
  • Inserting Data into the Card: Once the data is “loaded” (simulated with a setTimeout function), we’ll update the HTML elements with the actual content.
  • Removing the Skeleton Class: After the content is inserted, we’ll remove the skeleton class from the placeholders, revealing the final content.

And there you have it—a complete guide to creating a dynamic profile card with a skeleton loading effect. By combining HTML, CSS and JavaScript, we’ve built a functional, visually appealing component that’s perfect for any web application.

Feel free to experiment with different styles, animations and content to make the card your own. Whether you’re building a portfolio, a social media app or a dashboard, this card design will add a touch of professionalism to your project.

Also Read: Realistic Credit Card Animation using HTML CSS and JS

If you have any questions or ideas for further enhancements, let me know in the comment section.

Download