Skeleton Card
Pure-CSS animated card placeholder: a low-opacity linear gradient sweeps in a loop to simulate a shimmer loading state — zero JavaScript, zero dependencies.
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. Navigation has 36 effects, including 4 free. Explore the category →
3 usage examples



How it works
The shimmer relies on a single @keyframes shimmer rule that animates background-position from 200% 0 to -200% 0. Each skeleton zone's background is a linear-gradient(90deg) with very low-opacity stops (5% · 10% · 5%) and background-size: 200% 100% — as the position advances, the bright central band sweeps the surface left to right with no engine-side calculation.
Three zones reproduce a content card's anatomy: image (120 px height, border-radius: 8px), title (70% width, 16 px height), and text lines (100% · 90% · 60% width). Unequal proportions avoid a rigid grid feel and mimic the real typography of a product card or article.
The three lines carry staggered animation-delay values (0.1 s · 0.2 s · 0.3 s), creating a visible phase offset: the light appears to travel top-to-bottom rather than flashing simultaneously across the whole component.
The animation runs at a fixed duration of 1.5 s, looping infinite, with timing-function: ease. No JavaScript: the component is fully self-contained in CSS and integrates as-is into React, Vue, Angular, or Svelte without any initialisation.
Accessibility
- prefers-reduced-motion absent: the code contains no
@media (prefers-reduced-motion: reduce)rule. For users sensitive to continuous animation, add in your own stylesheet:.skeleton-image, .skeleton-title, .skeleton-line { animation: none; }guarded by the media query. - Skeleton elements are empty
<div>s — addaria-hidden="true"on.skeleton-cardandaria-live="polite"on the parent container so screen readers announce the state change once real content is injected. - The skeleton carries no interactive role and no focusable element: no keyboard navigation issue is inherent to the component.
- Very low opacities (5% / 10%) are intentional — the skeleton is a placeholder, not content; it does not reach WCAG contrast thresholds, which is expected.
- Indefinite duration: if content never loads, the animation loops forever — respect WCAG 2.2.2 by exposing a mechanism to stop or dismiss the skeleton after a reasonable timeout.
Browser compatibility
Relies solely on @keyframes, linear-gradient, and animated background-position — available in all modern browsers since 2015.
Without CSS animation support, skeleton zones display as static low-opacity blocks — no JavaScript is required, no error occurs.
The code
HTML structure to paste into your page (CSS + JS available with a premium account):
<div class="skeleton-card">
<div class="skeleton-image"></div>
<div class="skeleton-content">
<div class="skeleton-title"></div>
<div class="skeleton-text">
<div class="skeleton-line"></div>
<div class="skeleton-line"></div>
<div class="skeleton-line"></div>
</div>
</div>
</div>
Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
| animation-duration (1.5 s) | 1.5s | Speed of the light sweep across all skeleton elements. Increase to 2.5 s for a softer feel, decrease to 0.8 s for a faster perceived load. |
| .skeleton-card max-width | 280px | Maximum card width. Set to 100% for a full-width card or adapt to your grid column size. |
| .skeleton-image height | 120px | Height of the image placeholder block. 157 px ≈ 16:9 ratio at 280 px wide, 280 px = perfect square. |
| gradient opacity stops | .05 / .10 / .05 | Opacity of the rgba(255,255,255,…) stops. Raise to .10 / .20 / .10 for a more visible shimmer; on light backgrounds invert to rgba(0,0,0,…). |
| .skeleton-title width | 70% | Width of the title placeholder bar. Range from 40% to 85% depending on the typical length of your real titles. |
| animation-delay per line | .1s / .2s / .3s | Phase offset between lines. Increase to .2 s / .4 s / .6 s for a more pronounced cascade; set to 0 s on all for a synchronised shimmer. |
| border-radius .skeleton-card | 12px | Card corner radius. 0 px for a full-width list layout, 16–20 px for a floating card style. |
FAQ
display: none) the .skeleton-card element and inject the real card's HTML in its place. Add a fade-in transition (opacity 0→1 over 200 ms) on the real card for an invisible handoff.skeleton-* classes are shared. Include the CSS once in your page or bundle, then instantiate as many .skeleton-card elements as needed. In React or Vue, create a <SkeletonCard /> component that renders only the skeleton HTML with no additional style.#0a0a0f). On a light background, invert the stops: replace rgba(255,255,255,.05) with rgba(0,0,0,.05) and rgba(255,255,255,.10) with rgba(0,0,0,.12), and set .skeleton-card background to rgba(0,0,0,.04).