LoadersFree

Card Skeleton

Pure-CSS card skeleton: circular avatar 48 px, image block 120 × 280 px and three body lines — seven zones animated by a 10%-opacity white shimmer crossing each one in 1.8 s, all in phase simultaneously.

CSSShimmerCard
Hover or click the scene to interact

This effect is free — the Loaders category contains 29 effects total, including 4 free. Effect.Labs has 811 vanilla effects. Explore the category →

Usage examples

Relay
For you Following
Loading the feed…

① Publications feed — articles loading on first render

WhenA timeline (blog, internal social feed, newsletter) loads its cards on initial display or when opening a section whose data comes from an API.
WhyThe avatar + image + text-lines structure exactly mirrors the shape of a post with author photo, cover image and excerpt — the eye recognises the expected layout before the data arrives.
SettingsStack 2–3 .card-skel instances side by side (CSS grid, gap: 16px). Reduce .card-skel-img to 80 px for a vertical list with a horizontal thumbnail.
Esquisse
Popular Recent Following
Loading shots…
Léa Moreau
Ondine Studio · Pro
Nova Bank app redesign
Mobile · Fintech · Dark mode
♥ 1,24818.3k views

② Design gallery — shots arriving one by one

WhenA Dribbble- or Behance-style gallery fills its grid when the tab changes (Popular → Recent) or on scroll: a project's metadata arrives fast, its high-resolution thumbnail — often several hundred KB — a few tenths of a second later.
WhyThis skeleton's anatomy is exactly that of a shot: creator avatar and name on top, project visual in the middle, title, tags and counters below. Each of the seven zones has an exact real counterpart — the 48 px avatar becomes the creator's photo, the subtitle their studio, the block becomes the thumbnail, and the 100 / 100 / 60 % lines become the title, the tag line and the short counter line. A header-less skeleton (fx-0469) would make the creator pop in at once; here, when the data lands, nothing moves by a pixel.
SettingsCard narrowed to 244 px (two per row on 540 px) with padding: 14px; .card-skel-img raised from 120 to 148 px (≈ 3:2 visual) with 12 px margins; 48 px avatar, 10 px lines, 1.8 s duration and opacities kept as shipped.
Testimonials
They chose Lumen
★ 4.9/5 · 312 verified reviews
Sarah Benali
Founder, Atelier Kumo

“Our bookings doubled in three months. The team replies within the hour, even on weekends.”

③ Customer testimonials — video reviews loading on a sales page

WhenThe testimonials section of a sales page receives its reviews from a third-party platform (verified reviews) after first render: photo, name, video thumbnail and quote arrive in one batch, a few hundred milliseconds after the rest of the page.
WhyOn a sales page, a layout shift right above the buy button costs conversions: this skeleton reserves exactly the footprint of the final card — 48 px avatar, two-line header, 120 px block, three quote lines — with no JSON dimension altered. The round avatar already says “a person is speaking”, which no spinner does, and the 100 / 100 / 60 % fall-off mirrors that of a three-line quote. A circular loader in the middle of the card would have neither the shape nor the promise.
SettingsJSON dimensions kept (48 px avatar, .card-skel-img 120 px, 10 px lines); only the width goes from 280 to 244 px with padding: 16px to fit two cards per row. The image block stands in for the video testimonial thumbnail; remove it for text-only reviews.

How it works

The component uses two base classes. .skel-bone-circle (the avatar, 48 × 48 px, border-radius: 50%) and .skel-bone (six rectangular surfaces) share the same ghost-grey background rgba(255, 255, 255, 0.08), position: relative and overflow: hidden. The .card-skel wrapper is 280 px of inner content with 20 px padding — rendered at 322 px including borders. The header row uses display: flex; gap: 14px: the avatar takes 48 px, the remaining div (flex: 1, 218 px) holds the title (14 px tall, 65 % = 142 px) and the subtitle (10 px, 45 % = 98 px).

The shimmer is carried by the ::after pseudo-element of each zone. It is positioned at position: absolute; inset: 0 and carries background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.10) 50%, transparent 100%) / 200% 100%. @keyframes shimmerSlide animates background-position from -200% 0 to 200% 0 over 1.8 s ease-in-out infinite. With a background image 200 % wide and the percentage-position formula (offset = p/100 × (W − 2W)), the bright center starts three element-widths off the right edge and ends one width off the left — the shimmer sweeps right to left. It is visible for about 25 % of the cycle (≈ 0.45 s).

The seven zones shimmer in sync: no animation-delay rule is present and all share delay: 0s. The three body lines measure 100 %, 100 % and 60 % (168 px) in width. The second line was intended to show 92 % via the CSS rule .card-skel-line:nth-child(2), but that rule never matches: the lines sit at positions 3, 4 and 5 inside .card-skel (after .card-skel-header and .card-skel-img). The :last-child rule at 60 % does apply correctly. The code contains no JavaScript.

Accessibility

  • prefers-reduced-motion not handled (measured): under prefers-reduced-motion: reduce emulation in Playwright, all seven zones keep animating (animationPlayState: running). Add to your stylesheet: @media (prefers-reduced-motion: reduce) { .skel-bone::after, .skel-bone-circle::after { animation: none; } }.
  • No ARIA attributes are present in the shipped code. To signal a loading state: add role="status" and aria-label="Loading" to .card-skel, or set aria-busy="true" on the parent container until the real content is injected.
  • The skeleton elements are empty divs — screen readers announce nothing by default. If a role="status" handles the announcement, add aria-hidden="true" to .card-skel to avoid double-vocalisation.
  • No focusable or interactive element is present: the component is passive. Keyboard navigation is unaffected.

Browser compatibility

Requires @keyframes, linear-gradient, background-size, animated background-position, position: relative/absolute, inset and overflow: hidden. No site CSS variables (var(--…)) — works on a blank page with no external context.

Chrome 87+✓ Full
Firefox 87+✓ Full
Safari 14.1+✓ Full
Edge 87+✓ Full
Mobile iOS 14.5+✓ Full
Android Chrome 87+✓ Full

Without CSS animations (very old browser or disabled hardware acceleration), the zones display as static semi-transparent blocks — the loading structure remains understandable. The inset property requires Chrome 87 / Firefox 87 / Safari 14.1; on older browsers replace it with top: 0; right: 0; bottom: 0; left: 0.

The code

Copy the three blocks into your page. No dependencies.

HTML
<div class="card-skel">
  <div class="card-skel-header">
    <div class="skel-bone-circle card-skel-avatar">
    </div>
    <div style="flex:1">
      <div class="skel-bone card-skel-title">
      </div>
      <div class="skel-bone card-skel-subtitle">
      </div>
    </div>
  </div>
  <div class="skel-bone card-skel-img">
  </div>
  <div class="skel-bone card-skel-line">
  </div>
  <div class="skel-bone card-skel-line">
  </div>
  <div class="skel-bone card-skel-line">
  </div>
</div>
CSS
.skel-bone  {
   background: rgba(255, 255, 255, 0.08);
   border-radius: 6px;
   position: relative;
   overflow: hidden;
   }

.skel-bone-circle  {
   background: rgba(255, 255, 255, 0.08);
   border-radius: 50%;
   position: relative;
   overflow: hidden;
   }

.skel-bone::after, .skel-bone-circle::after  {
   content: "";
   position: absolute;
   inset: 0px;
   background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.1) 50%, transparent 100%) 0% 0% / 200% 100%;
   animation: 1.8s ease-in-out 0s infinite normal none running shimmerSlide;
   }

.card-skel  {
   width: 280px;
   padding: 20px;
   background: rgba(255, 255, 255, 0.03);
   border-radius: 16px;
   border: 1px solid rgba(255, 255, 255, 0.06);
   }

.card-skel-header  {
   display: flex;
   align-items: center;
   gap: 14px;
   margin-bottom: 20px;
   }

.card-skel-avatar  {
   width: 48px;
   height: 48px;
   flex-shrink: 0;
   }

.card-skel-title  {
   height: 14px;
   width: 65%;
   margin-bottom: 8px;
   }

.card-skel-subtitle  {
   height: 10px;
   width: 45%;
   }

.card-skel-img  {
   height: 120px;
   width: 100%;
   margin-bottom: 16px;
   border-radius: 10px;
   }

.card-skel-line  {
   height: 10px;
   width: 100%;
   margin-bottom: 10px;
   }

.card-skel-line:nth-child(2)  {
   width: 92%;
   }

.card-skel-line:last-child  {
   width: 60%;
   margin-bottom: 0px;
   }

.toggle-skel-bones .skel-bone-circle, .toggle-skel-bones .skel-bone  {
   }

@keyframes shimmerSlide  {
   
  0%  {
   background-position: -200% 0px;
   }
  100%  {
   background-position: 200% 0px;
   }
}
JavaScript (fx-0439)
// Effet CSS pur — pas de JavaScript nécessaire

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Shimmer duration (CSS — animation: 1.8s) 1.8s Speed of the full cycle. 1.2 s = fast perceived load, 2.5 s = slower pace. The bright band is visible for ≈ 25 % of the cycle (0.3 s at 1.2 s, 0.625 s at 2.5 s).
Shimmer peak opacity (CSS — rgba(255, 255, 255, 0.10)) 0.10 Maximum brightness at the gradient centre. Raise to 0.18–0.22 for a light theme or saturated dark background; lower to 0.06 for a subtle shimmer.
Zone base opacity (CSS — rgba(255, 255, 255, 0.08)) 0.08 Ghost-grey tint of the bone at rest. 0.05 = very discreet, 0.12 = clearly visible zones. Replace rgba(255,255,255,…) with rgba(0,0,0,…) on a light background.
Card width (CSS — .card-skel { width: 280px }) 280px Switch to 100% for a responsive card. The shimmer adapts automatically: percentage widths follow the card, the 200%-wide gradient follows each element.
Avatar size (CSS — .card-skel-avatar { width: 48px; height: 48px }) 48 px The flex: 1 wrapper adjusts automatically. Update the calculation if you specify title widths in pixels.
Image block height (CSS — .card-skel-img { height: 120px }) 120 px 180 px ≈ 16:9 ratio on 280 px. Remove .card-skel-img from the HTML for an image-free card. The border-radius: 10px on this zone can be harmonised with your design system.
Body line widths 100 % / 100 %* / 60 % The last line is 60 % via :last-child. The :nth-child(2) rule at 92 % never fires (see FAQ). For a real decreasing cascade, add .card-skel-line:nth-child(4) { width: 92%; }.

FAQ

What is the difference from Skeleton Card (fx-0469), which has almost the same name?

fx-0469 has five elements with no avatar (image + title + three lines), staggered animation-delay values (0.1 s / 0.2 s / 0.3 s) and a left-to-right shimmer over 1.5 s ease. fx-0439 has seven elements with a circular avatar and a subtitle, all simultaneous, right-to-left shimmer over 1.8 s ease-in-out. Use fx-0439 when the real card includes an author identity or profile; fx-0469 when the card has no header.

Why is the second body line 100 % wide instead of the intended 92 %?

The rule .card-skel-line:nth-child(2) targets the 2nd direct child of .card-skel. The lines sit at positions 3, 4 and 5 (after .card-skel-header and .card-skel-img): the rule never matches. To fix it: .card-skel-line:nth-child(4) { width: 92%; }.

Can I remove the image block and keep just the header and text lines?

Yes — remove <div class="skel-bone card-skel-img"></div> from the HTML. The CSS adapts without changes: :last-child still applies to the third line, the margin-bottom: 16px from the image disappears and the card compresses accordingly. No JavaScript to change.