Loaders✨ Premium

Profile Skeleton

Ten CSS bones in a column: an 80 px circle, a name (130×16 px), two bio lines and three stats blocks — synchronous 1.8 s shimmer, zero JavaScript.

CSSShimmerProfil

You'll get access to the interactive demo with a free account.

This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Loaders has 29 effects, including 4 free. Explore the category →

3 usage examples

Social networking app — user profile loading screen — Profile Skeleton example 1

① Social networking app — user profile loading screen

WhenProfile screen of a mobile or web app: the user tapped an avatar in their feed and the page opens while network data arrives in 300 to 800 ms.
WhyThe skeleton's anatomy — large circle, name, short bio, stats — matches the structure of an Instagram, Twitter or LinkedIn profile exactly: the user immediately recognises the incoming layout. An anonymous spinner carries no such expectation.
SettingsAvatar enlarged to 96 px (width/height: 96px), name at width: 150px; height: 18px, bone fill raised to rgba(255,255,255,.1) for sharper contrast on a dark #111 background.
Forum or community — author card on a message thread — Profile Skeleton example 2

② Forum or community — author card on a message thread

WhenIn a discussion thread, each post shows a compact author card in a sidebar column (avatar, handle, participation stats). The card loads on demand when the thread becomes visible.
WhyThe skeleton is compact (260 px wide) and vertical, fitting naturally in an aside column. The three stats blocks stand in for participation metrics — topics, replies, reactions — which are typically among the first data requested.
SettingsAvatar reduced to 56 px, name at width: 100px; height: 14px, both bio lines removed (not present on a short author card), stats blocks kept.
HR dashboard — employee card with deferred loading — Profile Skeleton example 3

③ HR dashboard — employee card with deferred loading

WhenA grid of employee cards loads lazily: off-screen cards receive the skeleton first, then real data when they enter the viewport.
WhyThe profile layout (centred circle + numbered metrics) fits an HR or company directory card naturally. The three stats blocks can represent KPIs — projects, absences, seniority. No extra JavaScript needed: an IntersectionObserver simply removes the skeleton when the data arrives.
SettingsBone fill at rgba(255,255,255,.06) on a light-theme grid (background #f8f8f8), shimmer peak at rgba(0,0,0,.08).

How it works

The HTML exposes a single column: .profile-skel (260 px wide, flex column, centered items, gap: 12 px) holds four bones in the main column, then .profile-skel-stats (flex row, gap: 20 px, margin-top: 8 px) lays out three blocks side by side. Two classes carry the bones: .skel-bone for rectangles (border-radius: 6 px) and .skel-bone-circle for the avatar (border-radius: 50%). Measured dimensions: avatar 80×80 px, name 130×16 px, bio1 200×10 px, bio2 160×10 px — the two different widths suggest natural text. Each stats block holds a number bone (36×18 px, border-radius: 4 px) and a label bone (48×9 px, border-radius: 4 px). Base fill: rgba(255,255,255,.08). Zero JavaScript.

The shimmer lives entirely in pseudo-elements: .skel-bone::after and .skel-bone-circle::after cover each bone (position: absolute; inset: 0) and carry a linear-gradient(90deg, transparent 0, rgba(255,255,255,.1) 50%, transparent 100%) set to 200% of the element width. The shimmerSlide animation moves background-position from -200% 0 to 200% 0: a centered bright stripe travels left to right across the bone, fades out, and repeats. Duration: 1.8 s, ease-in-out, 0 s delay, infinite loop.

All ten bones share a 0 s delay, so the shimmer crosses them all at once. Across the 260 px column, the bright stripe sweeps the entire profile simultaneously, with no top-to-bottom offset. The shimmer peak is rgba(255,255,255,.10) against a base fill of rgba(255,255,255,.08) — a two-point opacity gap, subtle on a very dark background (#0a0a0f). On a lighter surface or a light-theme page the contrast between bone and shimmer disappears: switch to rgba(0,0,0,.06) for the bone fill and rgba(0,0,0,.12) for the shimmer peak.

Accessibility

  • prefers-reduced-motion not handled. Measured with Playwright's reduce emulation: animationPlayState stays running on all ten bones. Add @media (prefers-reduced-motion: reduce) { .skel-bone::after, .skel-bone-circle::after { animation: none; } } — the bones stay visible (8% fill), only the sweep stops.
  • The skeleton is a loading indicator: the code carries no role="status", no aria-busy, no aria-live, and no text alternative. Wrap .profile-skel in a <div role="status" aria-label="Loading profile…">, or put those attributes on the parent container that will swap the skeleton out for real content.
  • All bones are empty, non-focusable div elements. Add aria-hidden="true" on .profile-skel when the role="status" is carried by a parent.
  • The width is hard-coded to 260 px. On a column narrower than 280 px — typical on mobile — the skeleton overflows. Replace width: 260px with width: min(260px, 100%) and adjust the individual bone widths to percentages or max-width.

Browser compatibility

Pure CSS: requires @keyframes, animation, linear-gradient, background-size, flexbox and ::after pseudo-elements. Zero JavaScript, zero dependencies.

Chrome 49+✓ Full
Firefox 52+✓ Full
Safari 9+✓ Full
Edge 15+✓ Full
Mobile iOS 9+✓ Full
Android Chrome 49+✓ Full

Without CSS (HTML email, RSS reader), the bones vanish and the area is empty. Without @keyframes (IE 9 and earlier), bones render as solid grey shapes with no sweep — a static placeholder. Without flexbox (IE 9 and earlier), the column and the stats row lose their layout.

The code

HTML structure to paste into your page (CSS + JS available with a premium account):

index.html — structure
<div class="profile-skel">
  <div class="skel-bone-circle profile-skel-avatar"></div>
  <div class="skel-bone profile-skel-name"></div>
  <div class="skel-bone profile-skel-bio1"></div>
  <div class="skel-bone profile-skel-bio2"></div>
  <div class="profile-skel-stats">
    <div class="profile-skel-stat">
      <div class="skel-bone profile-skel-stat-num"></div>
      <div class="skel-bone profile-skel-stat-label"></div>
    </div>
    <div class="profile-skel-stat">
      <div class="skel-bone profile-skel-stat-num"></div>
      <div class="skel-bone profile-skel-stat-label"></div>
    </div>
    <!-- … 1 autre .profile-skel-stat (3 au total) -->
  </div>
</div>
🔒 Unlock the full code — from €2.99 the first month

Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Avatar size (CSS — .profile-skel-avatar) 80×80 px Circle size. Width, height and border-radius: 50% are linked — keep them equal. 64 px suits a compact card; 96 px a full profile page.
Name width (CSS — .profile-skel-name) 130 px × 16 px Suggests the length of the username or full name. 100 px for a short handle; 180 px for a long name. The 16 px height matches a typical body text line.
Bio line widths (CSS — .profile-skel-bio1, .profile-skel-bio2) 200 px / 160 px The asymmetry (200/160) mimics a natural sentence break. Two equal lines look artificial. Remove .profile-skel-bio2 if the bio fits on a single line.
Shimmer duration (CSS — animation: 1.8s) 1.8 s Speed of the sweep. 1.2 s feels urgent; 2.4 s communicates a longer wait. Below 1 s the sweep looks jittery.
Bone base fill (CSS — rgba(255,255,255,.08)) rgba(255,255,255,.08) Fill color for rectangles and circle. On a light background, switch to rgba(0,0,0,.07). On a colored surface, use the page's dominant hue at 10–15% opacity.
Shimmer peak (CSS — rgba(255,255,255,.1) inside the gradient) rgba(255,255,255,.10) Brightness of the bright stripe. Increasing the gap with the base fill (e.g. .18) makes the sweep more visible. On a light background, replace with rgba(255,255,255,.9).
Number of stats blocks (.profile-skel-stat) 3 Remove one .profile-skel-stat for two metrics (posts, followers), or add a fourth if the page shows four counters. The flex parent redistributes them automatically.

FAQ

Card Skeleton represents a content card: a small avatar, title, subtitle, horizontal image and a few body lines — a unit in a list or grid. Profile Skeleton represents a page or section dedicated to a person: a large, centred circle (80 px, twice the card skeleton's size), two distinct bio lines and an evenly-spaced metrics row. The use case differs: card skeletons repeat in a feed; the profile skeleton is unique and centred on screen. In code, Card Skeleton includes JavaScript (for the loaded/skeleton state transition) and a rectangular image bone; Profile Skeleton is pure CSS.
Yes: add an animation-delay to each bone. Example: name 0 s, bio1 0.1 s, bio2 0.2 s, stats 0.3 s. The bright stripe then descends from top to bottom, as if scanning the profile. A short stagger (0.1 s) creates a scan impression; a longer one (0.3–0.5 s) makes a slow wave. The synchronous default is more understated and does not draw the eye to one bone at a time.
Replace width: 260px on .profile-skel with width: 100%; max-width: 260px, and switch fixed bone widths to percentages: 50% for the name, 77% for bio1, 62% for bio2. The stats row is already flex — just check that .profile-skel-stats stays flex-wrap: nowrap on mobile (three narrow columns) or switch to flex-wrap: wrap; gap: 12px for three independent blocks.