Loaders✨ Premium

Skeleton Screen

Three 16 px lines (60/100/80%) driven by a dark shimmer in 1.5 s — minimal text skeleton, synchronous, zero JavaScript.

CSSContent

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

Preview list — messages loading from the API — Skeleton Screen example 1

① Preview list — messages loading from the API

WhenA messaging app (email, SMS, chat) where each list item shows sender, subject and excerpt: data arrives in batch from the API.
WhyThe 60/100/80% template naturally mirrors a message preview: the short line stands for the sender name, the full-width one for the subject, the third for the content snippet. Synchronous shimmer on all three lines makes the block read as a single item in waiting.
Settings.loader-skeleton repeated 5 to 8 times in the list, gap: 20px between blocks, animation-duration: 2s for a calmer pace.
Response pending — placeholder in an AI generation area — Skeleton Screen example 2

② Response pending — placeholder in an AI generation area

WhenA chatbot or text-completion interface: the response bubble is empty while the model generates, before the text starts streaming.
WhyA plain-line skeleton (no card, no image) makes fewer layout promises than a full card skeleton — it does not commit to a format the response may not have. Synchronous shimmer avoids the distraction of a stagger during a wait the user already perceives.
Settingswidth: 100% on .loader-skeleton to fill the bubble width; height: 14px on .skeleton-line to match the chat font size.
Product page — the description waits for the catalogue API, photo and price already shown — Skeleton Screen example 3

③ Product page — the description waits for the catalogue API, photo and price already shown

WhenAn online store's product page: photo, price, rating and buy button come from cache and render immediately; only the descriptive paragraph is requested from the catalogue API and lands a few hundred milliseconds later.
WhyWhat is missing here is not the page but a paragraph. Three 16 px lines at 60/100/80% have exactly the silhouette of a text block: a short opening line like a product blurb, a full line, an interrupted last line — the eye reads "text is coming here, at this size", not "the page is loading". The skeleton reserves the paragraph's height, so the buy button does not jump when the description arrives. A spinner in the same spot would cast doubt on the whole page while everything else is already usable.
Settings.loader-skeleton at width: 100% of the text column (the original 200 px would leave a gap on the right); keep height: 16px, gap: 12px and border-radius: 4px: they match the body size and line spacing of the paragraph that will replace them. For a two-paragraph description, repeat the block once.

How it works

The skeleton relies on two classes. .loader-skeleton is a flex column 200 px wide with gap: 12px; it holds three .skeleton-line elements, each 16 px tall with a 4 px border radius. Widths are set directly in CSS: the first line is 60% (120 px), the second 100% (200 px), the third 80% (160 px) — a short-long-medium profile that mimics the natural variation of real text. The measured total height of the block is 72 px (3 × 16 px + 2 × 12 px gap).

The single @keyframes shimmer moves background-position from 200% 0 to -200% 0, sweeping the gradient peak from right to left across 400% of the width. The gradient — linear-gradient(90deg, #1a1a24 25%, #2a2a34 50%, #1a1a24 75%) with background-size: 200% 100% — uses two dark tones: the base at rgb(26, 26, 36) and the peak at rgb(42, 42, 52), a difference of 16 per RGB channel. Duration is 1.5 s, ease curve, no delay on any of the three lines: all three shimmer in phase. Measured in Chromium, the position moves from ~80% to ~-134% in 500 ms, giving ~270% per second.

Two limitations to know before integrating. First, the code contains no @media (prefers-reduced-motion: reduce) rule: measured in Playwright with the preference emulated, the animation runs at the same speed — stop it manually (see Accessibility). Second, the sold HTML wraps the skeleton in a .demo-preview — the site's demo container with background: #0a0a0f and min-height: 300px — which is not needed for the effect itself: the useful structure is only the .loader-skeleton and its three children (see HTML Structure).

Accessibility

  • prefers-reduced-motion not handled: no @media (prefers-reduced-motion: reduce) rule in the sold CSS — measured, the animation runs at 1.5 s regardless of the user's preference. Add @media (prefers-reduced-motion: reduce) { .skeleton-line { animation: none } } to freeze the shimmer for affected users.
  • Semantic role missing: add role="status" and aria-label="Loading" to .loader-skeleton (or to the parent element taking over), so screen readers can announce the loading state without reading through the three empty divs.
  • Alternative text: the three .skeleton-line elements are empty graphical elements. Add aria-hidden="true" to each; the text announced in the accessibility tree should come from the parent role="status", not from the children.
  • DOM removal: .loader-skeleton must be removed or hidden (display: none) as soon as real content is inserted, otherwise assistive technologies keep announcing a loading state for content that is already available.

Browser compatibility

Pure CSS — @keyframes, linear-gradient, background-size, animation: properties available in all current browsers without vendor prefixes since 2013. Zero dependencies.

Chrome 26+✓ Full
Firefox 16+✓ Full
Safari 7+✓ Full
Edge 12+✓ Full
Mobile iOS 7+✓ Full
Android Chrome 30+✓ Full

Without CSS animations (very old browsers), the three lines appear as uniform dark gray blocks with no movement — the skeleton layout stays intact, only the shimmer disappears.

The code

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

index.html — structure
<div class="loader-skeleton">
  <div class="skeleton-line"></div>
  <div class="skeleton-line"></div>
  <div class="skeleton-line"></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
Block width (CSS — .loader-skeleton { width }) 200px Total skeleton width; the line percentages are relative to it. 100% to fill the parent container; a fixed value to lock the layout.
Spacing between lines (CSS — .loader-skeleton { gap }) 12px Vertical distance between each line. 8px for a dense list; 20px to mimic a more open line height.
Line height (CSS — .skeleton-line { height }) 16px Thickness of each bar. Match the font size of the content being replaced: 14px for body text, 24px for a heading.
Corner radius (CSS — .skeleton-line { border-radius }) 4px 0 for rectangular bars (tabular style); 8px for a softer look; 9999px for pills.
Shimmer speed (CSS — animation-duration: 1.5s) 1.5s Duration of one full cycle. 2s: slower, less distracting; 0.8s: choppy, avoid for long waits.
Gradient colors (CSS — #1a1a24 / #2a2a34) dark gray pair, delta of 16/256 per channel For light backgrounds, replace with rgba(0,0,0,.06) / rgba(0,0,0,.14) for an adaptive semi-transparent shimmer. For a colored background, use two adjacent shades in the same palette.
Line widths (CSS — :nth-child(1/2/3) { width }) 60% / 100% / 80% Width profile of the lines. 100% / 90% / 60% for a narrowing paragraph; 30% / 100% / 100% to simulate a label followed by two data lines.

FAQ

Skeleton Text (fx-0470) distinguishes a heading (24 px, brighter white semi-transparent gradient) from a paragraph (14 px lines) with a 0.1 to 0.3 s stagger between lines. Skeleton Screen uses three uniform 16 px lines, synchronous, in absolute dark hex colors (#1a1a24 → #2a2a34). fx-0470 structures an editorial block with a title/body hierarchy; fx-0428 represents a row of equal-weight lines — data lists, messages, form fields.
No animation-delay is set on any line — all start at 0s. The synchrony is intentional: the block reads as a single visual unit rather than a perceptible cascade. To add a stagger, set .skeleton-line:nth-child(1) { animation-delay: 0s }, .skeleton-line:nth-child(2) { animation-delay: .15s }, .skeleton-line:nth-child(3) { animation-delay: .3s }.
The bars (#1a1a24 = very dark gray) show clearly on a white background, but the shimmer itself (a delta of 16/256 between the two tones) can appear faint. For light backgrounds, replace the gradient with linear-gradient(90deg, rgba(0,0,0,.06) 25%, rgba(0,0,0,.14) 50%, rgba(0,0,0,.06) 75%): the relative contrast stays the same but adapts to any background hue.