Skip to content
ssntl/ui

Loading shimmer

A slanted beam that crosses a loading placeholder, transform-only and respecting reduced motion.

Loading card

Installation

$ pnpm dlx shadcn@latest add @ssntl/shimmer

Usage

css
/* app/globals.css */
@import "../styles/ssntl/shimmer.css";

/* In your JSX */
<div aria-busy="true">
  <span class="sr-only">Loading</span>
  <div aria-hidden class="shimmer h-4 w-1/2 rounded-full"></div>
</div>
  • A slanted beam with a brighter core crosses left to right on --ease-in-out.
  • Only translate animates: it runs on the compositor, no repaints.
  • Speed and angle with [--shimmer-duration:2000ms] and [--shimmer-angle:100deg].
  • With reduced motion the beam disappears; the state lives in aria-busy and text, never only in the animation.

Code

styles/ssntl/shimmer.css
/* Loading shimmer: a soft slanted beam with a brighter core that crosses a
   placeholder left to right on --ease-in-out, so the slash eases instead of
   snapping. Only translate animates, so it runs on the compositor.

     shimmer                          the placeholder and its beam
     [--shimmer-duration:2000ms]      speed
     [--shimmer-angle:100deg]         slant

   With reduced motion the beam is removed and the placeholder stays still;
   the loading state lives in aria-busy and text, never in the animation. */

:root,
[data-theme="light"],
.light {
  --shimmer-highlight: rgb(255 255 255 / 0.75);
}
[data-theme="dark"],
.dark {
  --shimmer-highlight: rgb(255 255 255 / 0.07);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"], .light) {
    --shimmer-highlight: rgb(255 255 255 / 0.07);
  }
}

@utility shimmer {
  position: relative;
  isolation: isolate;
  overflow: clip;
  background-color: var(--shimmer-base, var(--secondary));

  &::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: linear-gradient(
      var(--shimmer-angle, 110deg),
      transparent 25%,
      color-mix(in oklab, var(--shimmer-highlight), transparent 55%) 42%,
      var(--shimmer-highlight) 50%,
      color-mix(in oklab, var(--shimmer-highlight), transparent 55%) 58%,
      transparent 75%
    );
    translate: -100% 0;
    animation: shimmer var(--shimmer-duration, 1600ms) var(--ease-in-out)
      infinite;
  }

  @media (prefers-reduced-motion: reduce) {
    &::after {
      display: none;
    }
  }
}

@keyframes shimmer {
  to {
    translate: 100% 0;
  }
}