Home / Snippets / UI Components /

Shimmer/skeleton

Animated shimmer gradient sweeps across placeholder shapes to show content structure while data loads.

Widely Supported
uianimation

Quick implementation

/* HTML:
<div class="skel-card" aria-label="Loading" aria-busy="true">
  <div class="skel skel--image"></div>
  <div class="skel skel--title"></div>
  <div class="skel skel--line"></div>
  <div class="skel skel--line-short"></div>
</div> */

@keyframes shimmer {
  from { background-position: -200% center; }
  to   { background-position:  200% center; }
}

.skel {
  border-radius: 0.375rem;
  background: oklch(0.22 0.02 260);
  background-image: linear-gradient(
    90deg,
    oklch(0.22 0.02 260) 0%,
    oklch(0.30 0.02 260) 40%,
    oklch(0.22 0.02 260) 80%
  );
  background-size: 200% 100%;
  animation: shimmer 1.6s ease-in-out infinite;
}

/* Shape modifiers */
.skel--avatar {
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 50%;
}

.skel--title  { height: 1rem;    width: 60%; }
.skel--line   { height: 0.75rem; }
.skel--line-short { height: 0.75rem; width: 75%; }
.skel--image  { height: 9rem; border-radius: 0.5rem; }

/* Card container */
.skel-card {
  background: oklch(0.19 0.02 260);
  border-radius: 0.75rem;
  border: 1px solid oklch(0.28 0.02 260);
  padding: 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

@media (prefers-reduced-motion: reduce) {
  .skel {
    animation: none;
    background-image: none;
  }
}

Prompt this to your LLM

Includes role, constraints, two framework variants, and edge cases to handle.

You are a senior frontend engineer building loading state UI.

Goal: A skeleton loading placeholder with an animated shimmer gradient that sweeps across placeholder shapes (avatar circle, title bar, text lines, image block). Pure CSS animation, no JavaScript.

Technical constraints:
- The shimmer is a linear-gradient on background-image with background-size: 200% 100%, animated via @keyframes from background-position: -200% center to 200% center.
- Use two oklch() values on the gradient stops — a base tone and a lighter highlight — both at low chroma to stay neutral.
- All shape variants are modifier classes on a shared .skel base — avatar uses border-radius: 50%, lines use fixed heights, image block uses a taller fixed height.
- The card container uses display: flex with flex-direction: column and gap to space skeleton rows.
- Add aria-busy="true" and aria-label="Loading" to the container for screen readers.
- Wrap the animation in @media (prefers-reduced-motion: reduce) and set animation: none with a static fallback color.

Framework variant (pick one):
A) Vanilla HTML + CSS — static markup matching the shape of the real content.
B) React component — accepts a shape prop ('card' | 'list-item' | 'profile') and renders the appropriate skeleton layout; removes itself when a loading prop becomes false.

Edge cases to handle:
- Multiple skeletons on the same page: animation should be synchronised (same duration/timing), not offset per element.
- When content loads: replace skeleton with real content without layout shift — skeleton dimensions must match real content dimensions.
- Dark and light themes: gradient stops should use CSS custom properties so they theme-switch cleanly.
- Reduced motion: skeleton becomes a static muted shape with no animation — still visually differentiates from real content.

Return HTML + CSS.

Why this matters in 2026

Blank white screens or spinning loaders leave users with no spatial context about what is coming. Skeleton screens preserve the layout shape of the content being fetched, so the user's eye is already in the right place when the real data arrives. The shimmer animation adds just enough motion to signal activity without being distracting — and because it is a pure CSS @keyframes animation on background-position, it runs on the compositor thread and adds zero JavaScript to the bundle.

The logic

The shimmer effect works by assigning a linear-gradient to background-image that is wider than the element — set via background-size: 200% 100%. The @keyframes animation slides the gradient from -200% to 200% on the horizontal axis, creating the illusion of light sweeping across. Each skeleton shape is a modifier class on a shared .skel base, controlling only the dimensions and border-radius. This means a single @keyframes definition and one set of gradient stops power every shape in the layout, keeping the CSS minimal.

Accessibility & performance

The container element should carry aria-busy="true" and aria-label="Loading content" so screen readers announce that a region is loading rather than reading out a stream of meaningless empty divs. When content loads, swapping the skeleton for real content (or setting aria-busy="false") triggers an update announcement. The @media (prefers-reduced-motion: reduce) query removes the animation entirely and falls back to a static muted tone — the shapes still communicate "placeholder" without any movement. The shimmer animates only background-position, which is handled by the compositor and does not trigger layout or paint on surrounding elements.