Profile Card with Skeleton loader using HTML CSS JS

In today’s fast web applications, skeleton loading screens have become a popular design trend to improve user experience. Instead of showing a blank or flickering page while data loads, skeleton loaders display placeholder animations that mimic the final layout. In this tutorial, we’ll build a Profile Card with Skeleton Loader using HTML, CSS and JavaScript that transitions smoothly from a loading state to displaying actual user data.

This is a simple yet powerful front-end project that showcases how to combine DOM manipulation, CSS animation and JavaScript timing to create a realistic loading experience.

HTML Structure

We’ll begin with the HTML structure of profile card with skeleton loader. The markup defines all the necessary elements, such as:

<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>
  • A cover section for the background image.
  • A circular avatar for the user profile picture.
  • Text placeholders for name, title and bio.
  • Social icons for platforms like Instagram, Twitter, Facebook and YouTube.

Each element initially has the class skeleton, which will be styled with a shimmering animation to simulate loading. The loading class applied to the main container ensures that the skeleton effect is active until data is fully loaded.

This structure allows a seamless transition between the loading placeholder and the final content of profile card with skeleton loader.

CSS Styling

The CSS gives the profile card with skeleton loader both its appearance and the skeleton shimmer effect.

@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;
    }
}
  1. Layout & Card Design:
    • The profile card is centered using Flexbox and styled with rounded corners, shadows and proper spacing.
    • The cover-wrapper and avatar-wrapper create distinct areas for the banner and the user’s profile picture.
  2. Skeleton Effect:
    • The .skeleton class uses a linear gradient background that shifts horizontally using the @keyframes shimmer animation.
    • This creates a smooth loading shine effect, commonly seen on platforms like Facebook or LinkedIn.
  3. Responsive Design:
    • The layout adapts well on smaller devices, ensuring the card remains perfectly visible on all screen sizes.
  4. Social Icons:
    • Each icon is styled with brand colors and hover effects, giving a clean, modern appearance once the content is loaded.

Together, these styles make the component both elegant and interactive while ensuring the skeleton effect looks natural.

JavaScript Functionality

The JavaScript handles data population and UI transition:

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")
    })
}
  • A userData object stores mock user information like name, title, about text and image URLs.
  • document.querySelector() selects the target elements for updating content dynamically.
  • insertUserDetails() injects all real content (text and images) into the DOM.
  • removeSkeleton() removes all skeleton classes to stop the shimmer animation.
  • setTimeout() creates a delay to simulate an API loading time before executing these functions.

This simple, modular JS setup makes the code easy to maintain and extend — you can later replace the mock data with a real API call using fetch().

Logic (How the Skeleton Loader Works)

Here’s how the logic behind the profile card skeleton loader works:

  1. Initial Loading Phase: When the page loads, all visible elements (cover, avatar, text fields and social icons) display skeleton placeholders.
  2. Simulated Data Fetching: A JavaScript setTimeout() simulates an API call by delaying data insertion for a few seconds (in this case, 3 seconds).
  3. Data Replacement: Once the delay ends, JavaScript dynamically injects real data (profile image, cover image, username, title and description) into the DOM.
  4. Smooth Transition: After content insertion, the .skeleton and .loading classes are removed, revealing the final, fully styled profile card.

This approach closely mimics how real-world web apps handle asynchronous content loading — improving user engagement and reducing perceived waiting time.

Final Thoughts

This Profile Card with Skeleton Loader is a great front-end mini-project to practice animation, DOM manipulation and asynchronous behavior.

It provides a real-world simulation of dynamic data fetching — a concept used in modern web apps like LinkedIn, Twitter and YouTube.

You can take this project further by:

  • Integrating real data from an API,
  • Adding fade-in transitions for smoother reveals,
  • Or using local storage to save user profiles.

With just a few lines of code, you’ve created a clean, professional and visually appealing profile card with skeleton loader that enhances user experience and makes your UI feel fast and dynamic.

Leave a Comment

Scroll to Top