Shimmer

Updated: Sep 24, 2026
Use Shimmer to represent the structure of content while it loads. The application builds a restrained placeholder from ordinary layout and ShimmerItem blocks, and Shimmer sweeps one tilted highlight across the whole group for a coherent loading state.
Prefer broad shapes that resemble the eventual interface, especially containers, with clear space between independent elements. Avoid geometry that is more detailed than the content it represents, and keep internal placeholder structure out of the accessibility tree. The application should replace the placeholder as soon as useful content or an error state is available.

Usage

import {
  Shimmer,
  ShimmerItem,
  ShimmerItemCornerRadius,
} from '@wearables-ui-toolkit/mrbd';

export function ListLoadingPlaceholder() {
  return (
    <Shimmer>
      <div className="loading-row">
        <ShimmerItem
          className="loading-avatar"
          cornerRadius={ShimmerItemCornerRadius.XLARGE}
        />
        <div className="loading-copy">
          <ShimmerItem className="loading-title" />
          <ShimmerItem className="loading-detail" />
        </div>
      </div>
    </Shimmer>
  );
}
.loading-row {
  display: flex;
  gap: var(--uit-spacing-medium);
  align-items: center;
}

.loading-copy {
  display: grid;
  flex: 1;
  gap: var(--uit-spacing-small);
}
Prefer semantic layout tokens and responsive CSS when sizing placeholder classes. This keeps the loading state aligned with the content across viewport sizes.
Keep the same outer tree mounted and update showShimmer when real content arrives. Shimmer then removes its visual treatment without changing the wrapper’s layout:
import { Shimmer, ShimmerItem } from '@wearables-ui-toolkit/mrbd';
import { ArticleBody } from './ArticleBody';

export function Article({ isLoading }: { isLoading: boolean }) {
  return (
    <Shimmer showShimmer={isLoading}>
      {isLoading ? <ShimmerItem className="loading-title" /> : <ArticleBody />}
    </Shimmer>
  );
}
Respect the user’s reduced-motion preference by holding the sweep still instead of animating it:
import {
  Shimmer,
  ShimmerItem,
  usePrefersReducedMotion,
} from '@wearables-ui-toolkit/mrbd';

export function QuietPlaceholder() {
  const prefersReducedMotion = usePrefersReducedMotion();

  return (
    <Shimmer autoStart={!prefersReducedMotion}>
      <ShimmerItem className="loading-title" />
    </Shimmer>
  );
}

Props

Both components extend standard div attributes and forward a ref to their root element. Props not listed below are spread onto that root.

Shimmer

The full prop type is ShimmerProps.
PropTypeDefaultDescription
children
ReactNode
—
Placeholder content. One shared highlight sweeps everything inside.
showShimmer
boolean
true
Applies the shimmer treatment. When false, children render unchanged.
autoStart
boolean
true
Runs the sweep. When false, the highlight remains at its resting position.
clipToChildren
boolean
false
Clips the sweep to the wrapper’s box rather than allowing it to overflow.
repeatCount
number
-1
Number of repeats after the first pass. A negative value repeats forever.
repeatMode
ShimmerRepeatMode
ShimmerRepeatMode.RESTART
Restarts each repeat from the same side or reverses its direction.
startDelayMs
number
0
Delay before the first sweep, in milliseconds. Negative values produce no delay.
staticAnimationProgress
number \| null
null
Pins the sweep at a fixed position from 0 to 1 instead of animating. The toolkit clamps out-of-range values.
className
string
''
Class added to the wrapper.
style
CSSProperties
{}
Inline styles applied to the wrapper. They merge after the component’s custom properties.

ShimmerItem

The full prop type is ShimmerItemProps.
PropTypeDefaultDescription
width
number \| string
—
Item width. A number is treated as pixels; prefer responsive CSS.
height
number \| string
—
Item height. A number is treated as pixels; prefer responsive CSS.
cornerRadius
ShimmerItemCornerRadius
ShimmerItemCornerRadius.MEDIUM
Semantic placeholder radius.
className
string
''
Class applied to the item.
style
CSSProperties
{}
Inline styles applied to the item. They merge after width and height.

Types and enums

ShimmerRepeatMode
  • RESTART — every repeat sweeps in the same direction
  • REVERSE — repeats alternate direction
ShimmerItemCornerRadius
  • SQUARE — no rounding
  • XXSMALL
  • XSMALL
  • SMALL
  • MEDIUM
  • LARGE
  • XLARGE
Every value except SQUARE maps to the matching corner-radius token. Choose the matching token when a placeholder block needs to remain concentric with the real component it represents.

Behavior

One shared sweep.Shimmer renders its children once and masks that single subtree. It does not duplicate content, so ids, data attributes, and refs inside the placeholder remain unique. One tilted highlight travels across the wrapper, helping several items read as one loading surface rather than unrelated animations.
Geometry. The toolkit derives the sweep distance from the wrapper’s measured box and recomputes it whenever the wrapper resizes. Responsive placeholders therefore keep the correct sweep without prop updates. When ResizeObserver is unavailable, the toolkit measures the box once on mount. The wrapper is an inline-block; give it width through application layout or CSS when it should fill a row.
Overflow. The sweep extends beyond the wrapper’s box by default. Set clipToChildren when the placeholder sits in a tight region and its highlight needs to remain inside that box.
Repetition. Each sweep uses the same fixed, non-configurable duration. startDelayMs controls only the delay before the first sweep. repeatCount counts repeats after that first pass, so a value of two produces three sweeps in total; the default negative value repeats forever. repeatMode restarts each repeat from the same edge or alternates its direction.
Base dimming. While showShimmer is on, the toolkit masks the wrapped content at a reduced base opacity, and the traveling highlight restores full opacity as it passes. This dimming belongs to the treatment rather than the animation, so it remains when the sweep is stopped.
Stopping.showShimmer={false} removes the complete treatment and renders children as ordinary full-opacity content. autoStart={false} keeps the treatment but parks the highlight at the start, leaving the content uniformly dimmed with no movement. Both values are inexpensive to toggle and preserve layout.
Static rendering.staticAnimationProgress stops the animation and pins the highlight between 0 and 1, which supports reproducible screenshots and other deterministic rendering. The toolkit clamps values outside that range.
Item appearance.ShimmerItem renders a solid block filled with var(--uit-color-loading-content) and uses the corner-radius token selected by cornerRadius. It has no intrinsic size, so the application provides width, height, or CSS. The item does not shrink in a flex row and keeps its assigned size.
Reduced motion. The CSS sweep does not stop automatically. The application must read the user’s preference with usePrefersReducedMotion and pass autoStart={false} or a fixed staticAnimationProgress when reduced motion is requested. This accessibility boundary prevents nonessential motion after the user has asked to limit it.

Accessibility

  • ShimmerItem is a visual placeholder and is always aria-hidden, so assistive technology does not announce its blocks.
  • Shimmer adds no role or accessible name. The application must expose loading status on the owning region, for example with a role="status" live region or aria-busy on the container being populated, and remove that status when content arrives.
  • Keep the surrounding layout stable when real content replaces the placeholder so focus does not move unexpectedly.
  • Do not place interactive or meaningful content inside Shimmer while the treatment is active. The mask changes its rendered opacity and does not represent usable content.
  • Represent the broad structure and footprint of the eventual interface.
  • Prefer container-like shapes and preserve clear separation between elements.
  • Match each placeholder block’s corner radius to the component it represents.
  • Replace shimmer as soon as content or a useful error state is available.

Avoid these patterns

  • Avoid placeholder geometry that is more detailed than the final content.
  • For work without a predictable content structure, prefer an IndeterminateLoader.
  • Do not nest one Shimmer inside another. One wrapper must own the complete placeholder region so the loading state has one coherent animation.
  • Prefer responsive sizing instead of a hardcoded device-sized layout.