webdevpuneet.com

Skeleton Profile — Free HTML CSS JS Loading Placeholder Card

Skeleton Profile · Loaders · Plain HTML, CSS & JS · Live preview

CategoryLoaders

Copy & paste

Source Code

<div class="sk-card" id="skCard">
  <div class="sk-cover"></div>
  <div class="sk-body">
    <div class="sk-avatar"></div>
    <div class="sk-line sk-w60"></div>
    <div class="sk-line sk-w40"></div>
    <div class="sk-stats">
      <div class="sk-stat"></div>
      <div class="sk-stat"></div>
      <div class="sk-stat"></div>
    </div>
    <div class="sk-line sk-w90"></div>
    <div class="sk-line sk-w80"></div>
    <div class="sk-actions"><div class="sk-btn"></div><div class="sk-btn sk-ghost"></div></div>
  </div>
</div>
<button type="button" class="sk-toggle" id="skToggle">Load content</button>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Layout-matched blocks
Placeholders sit where content will land.
GPU shimmer
translateX sweep, not background-position.
One shared animation
Every block shimmers in sync.
Varied line widths
Utility classes read as real text.
Loaded swap
is-loaded stops shimmer and colours in.
No layout shift
Geometry is in place before content.
Reduced-motion safe
Shimmer disables on user preference.
Fetch-ready
Toggle the class when data resolves.

About this UI Snippet

Skeleton Profile — A Shimmering Profile-Card Placeholder

Screenshot of the Skeleton Profile snippet rendered live

The skeleton profile is the grey, shimmering placeholder a profile card shows while its data loads — the cover, avatar, name, stats, and buttons all rendered as soft pulsing blocks so the layout is visible before the content arrives. This snippet builds it with plain HTML and CSS, plus a tiny toggle to demo the swap to real content.

Structure mirrors the real card

The skeleton's blocks are laid out exactly where the finished card's elements will be: an 80px cover band, a 64px round avatar overlapping it with a negative margin, name and subtitle lines of different widths, a three-up stats row, body lines, and two action buttons. Because the placeholder matches the real layout, the content doesn't jump or reflow when it loads — the whole point of a skeleton over a spinner is that the page geometry is already in place.

The shimmer sweep

Every placeholder block shares one shimmer: a ::after pseudo-element holds a horizontal linear-gradient highlight that animates with transform: translateX from -100% to 100% on a loop. Using a transform (rather than animating background-position) keeps the sweep on the GPU and perfectly smooth. One @keyframes and one shared rule drive the cover, avatar, lines, stats, and buttons together, so the entire card shimmers in sync.

Varying widths sell the realism

The text lines use utility width classes (sk-w90, sk-w60, sk-w40, etc.) so they look like real lines of differing length rather than identical bars. That small variety is what makes a skeleton read as "content loading" instead of "broken layout" — uniform bars look like an error, staggered widths look like text.

The loaded swap

Toggling an is-loaded class stops the shimmer (animation: none) and recolours the blocks into a finished-looking card — a gradient cover, a coloured avatar, and a primary button. In a real app you'd add that class the moment your fetch resolves; here a button toggles it so you can see both states. This models the exact handoff from placeholder to content.

Respecting reduced motion

A prefers-reduced-motion: reduce media query disables the shimmer for users who are sensitive to movement, leaving a static skeleton. Honouring that preference is an accessibility baseline for any looping animation, and it's a one-rule addition here.

Customizing it

Match the block sizes to your real card, change the shimmer colour and speed, or add more lines. Drop the loaded-state styles and instead render your actual content over the same skeleton container. Pair it with a skeleton card grid, a skeleton loader, or a skeleton dashboard.

Step by step

How to Use

  1. 1
    Paste HTML and CSSA shimmering profile-card skeleton renders.
  2. 2
    Watch the sweepA highlight glides across every block in sync.
  3. 3
    Click Load contentThe shimmer stops and the card colours in.
  4. 4
    Toggle backReturn to the skeleton state to compare.
  5. 5
    Wire to a fetchAdd is-loaded when your data resolves.
  6. 6
    Match your cardResize the blocks to your real layout.

Real-world uses

Common Use Cases

Profile page loading states
Show a cover, avatar, name, stats and button placeholders in the exact positions the real profile card will fill.
Card grid placeholders
Pair with a skeleton card grid so a whole page of cards loads with one consistent shimmer.
Generic shimmer reuse
Borrow the translateX sweep from a skeleton loader, which animates on the GPU rather than shifting background position.
Dashboard and list loading
Match a skeleton dashboard panel or an avatar status list so every region loads in step.
Team page placeholders
Fill the gap before a team card arrives, using varied line widths that read as real text and respect reduced-motion settings.
Related: Optimistic Action Button with Rollback on Failure
See the Optimistic Action Button with Rollback on Failure for a related loaders pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

A skeleton lays its placeholder blocks exactly where the real elements will be — cover, avatar, name, stats, buttons — so the page geometry is already in place and content does not jump or reflow when it loads. A spinner gives no sense of the layout and the page lurches when data arrives.

Each block has an ::after holding a horizontal gradient highlight that animates with transform: translateX from -100% to 100% on a loop. Using a transform keeps the sweep on the GPU compositor and perfectly smooth, and one shared keyframes and rule drive every block so the whole card shimmers in sync.

The text lines use utility width classes so they look like real lines of differing length. Uniform bars read as a broken layout, while staggered widths read as text that is loading — that small variety is what sells the skeleton as content arriving rather than an error state.

Yes. A prefers-reduced-motion: reduce media query disables the shimmer animation, leaving a static skeleton for users sensitive to movement. Honouring that preference is an accessibility baseline for any looping animation, and here it is a single rule.

Render the skeleton while a loading flag is true and the real card when it is false, or keep the same container and add an is-loaded class when your fetch resolves. Tie the flag to your data-fetching state (a loading boolean, React Query's isLoading, a Suspense fallback). The shimmer CSS ports unchanged; in Tailwind use animate utilities or a custom keyframe.