Skip to content

Skeleton Shimmer Loader

Loading-state placeholder blocks with a light sweep animating across them.

Freeminimalglass
Category
Assets
Added
2026-08-22
Deps
none
Updated
2026-09-01
Preview
Loading…

Props

3
Show avatar block
1.6
No runtime dependencies
"use client";

export function SkeletonShimmer({ rows = 3, avatar = true, speed = 1.6 }: {
  rows?: number;
  avatar?: boolean;
  speed?: number;
}) {
  return (
    <div
      role="status"
      aria-busy="true"
      className="flex w-full max-w-sm items-start gap-3"
      style={{ "--shimmer-speed": `${speed}s` } as React.CSSProperties}
    >
      <span className="sr-only">Loading…</span>
      {avatar && <div className="shimmer h-11 w-11 shrink-0 rounded-full bg-neutral-200" />}
      <div className="flex flex-1 flex-col gap-2.5 pt-1">
        {Array.from({ length: rows }).map((_, i) => (
          <div
            key={i}
            className="shimmer h-3 rounded-full bg-neutral-200"
            style={{ width: i === rows - 1 ? "60%" : "100%" }}
          />
        ))}
      </div>
      {/* speed comes in via the --shimmer-speed CSS variable set above, not
          a ${speed}s template interpolation inside this style block —
          interpolating directly into styled-jsx here compiles to a literal
          0s animation-duration, so the block stays fully static. */}
      <style jsx>{`
        .shimmer { position: relative; overflow: hidden; }
        .shimmer::after {
          content: "";
          position: absolute;
          inset: 0;
          transform: translateX(-100%);
          background: linear-gradient(90deg, transparent, rgba(255,255,255,0.7) 50%, transparent);
          animation: shimmer-sweep var(--shimmer-speed, 1.6s) ease-in-out infinite;
        }
        @keyframes shimmer-sweep {
          100% { transform: translateX(100%); }
        }
      `}</style>
    </div>
  );
}
Notes

About this asset

Loading placeholders are one of the few UI patterns where pure CSS genuinely beats a JS animation loop; there is no interactivity or state to track, just a sweep that has to run continuously and cheaply while real content loads. Each placeholder block shares one shimmer class whose after pseudo-element is an absolutely positioned gradient sweeping left to right on an infinite keyframe loop, and every block needs its own overflow-hidden and relative positioning so the sweep clips to that block's own rounded shape instead of one highlight sliding across the whole row as a single rectangle. The speed prop follows the same CSS-custom-property pattern used elsewhere in this library: set once via inline style as a shimmer-speed variable and read with var() inside the styled-jsx block, never interpolated directly into the template literal, which compiles to a static, non-functioning duration. The last line is intentionally rendered narrower than the rest to mimic a natural paragraph's line wrap. The whole thing is decorative markup with no semantic connection to the content it stands in for.

Curator’s note

One shared .shimmer class with overflow-hidden per block, not one giant sweep over the whole row — that's what keeps the highlight looking like it's moving across each rounded shape instead of just sliding over a rectangle.

Related

Pairs well with

Matched on shared tags and category — the entries most likely to be used alongside this one.

An nprogress-style top-of-page bar that eases toward completion, never stalling.

loadingprogressnprogress
Featured
NewPro

A button that survives a real round trip — a minimum pending duration, a width that never twitches, aria-disabled instead of disabled.

buttonasyncloading
Featured

A card grid with a soft red spotlight that follows the cursor across it.

gridspotlightcursor