tw-shimmer is a zero-dependency Tailwind CSS v4 plugin that provides polished shimmer animations for both text and skeleton loaders. It uses sine-eased gradients with 17 carefully calculated stops and OKLCH color mixing for smooth, banding-free effects.
- CSS-only — No JavaScript runtime, pure Tailwind utilities
- Text + Background — Shimmer text or skeleton placeholders
- Auto-sizing — CSS container queries size the animation track automatically
- Customizable — Speed, spread, angle, color, and timing
See the interactive demo for live examples.
Installation#
<Tabs items={["npm", "pnpm", "yarn"]}>
sh npm install tw-shimmer
sh pnpm add tw-shimmer
sh yarn add tw-shimmer
Add to your CSS:
@import "tailwindcss";
@import "tw-shimmer";
Quick Start#
Text Shimmer#
<span class="shimmer text-foreground/60">Loading...</span>
Skeleton Loader#
<div class="shimmer shimmer-bg bg-muted h-4 w-48 rounded" />
Skeleton Card with Auto-Sizing#
<div class="shimmer-container flex gap-3">
<div class="shimmer shimmer-bg bg-muted size-12 rounded-full" />
<div class="flex-1 space-y-2">
<div class="shimmer shimmer-bg bg-muted h-4 w-1/4 rounded" />
<div class="shimmer shimmer-bg bg-muted h-4 w-full rounded" />
<div class="shimmer shimmer-bg bg-muted h-4 w-4/5 rounded" />
</div>
</div>
API Reference#
Core Utilities#
shimmer#
Base utility for text shimmer. It keeps one text node and, when -webkit-mask-clip: text is supported, masks the host and animates an additive highlight band on the compositor. The fallback animates the text gradient.
The compositor band is additive and defaults to white. --shimmer-color, shimmer-color-*, and shimmer-invert select its color. The two paths match on white and dark surfaces; tinted surfaces can differ. Because the host is masked, keep non-text descendants such as icons outside it. Selection backgrounds are clipped to the glyphs on the compositor path.
Both paths hold still under prefers-reduced-motion: reduce, leaving the label in its plain text color. shimmer-bg keeps animating.
shimmer-bg#
Background shimmer for skeleton loaders. Requires the base shimmer class,
which paints and animates the gradient, and a base bg-* class.
<div class="shimmer shimmer-bg bg-muted h-4 w-64 rounded" />
shimmer-container#
CSS-only auto-sizing helper using container queries. It uses the container width as the animation track width. Because it establishes inline-size containment only, the height term for angled gradients uses the nearest block-size query container when one exists and otherwise falls back to the small viewport height; half the container width and 200px remain its minimums.
<div class="shimmer-container">
<div class="shimmer shimmer-bg bg-muted h-4 w-full rounded" />
</div>
Customization Utilities#
| Utility | Default (text) | Default (bg) | Description |
|---|---|---|---|
shimmer-speed-{n} | 200 | 1000 | Animation speed in px/s |
--shimmer-track-width | 200px | 200px | Track width for timing |
shimmer-spread-{n} | calc(4ch + 80px) | 480px | Shimmer highlight width |
shimmer-color-{color} | auto | auto | Highlight color (Tailwind palette) |
shimmer-angle-{deg} | 15 | 15 | Sweep angle in degrees |
shimmer-duration-{ms} | auto | auto | Fixed animation duration |
shimmer-repeat-delay-{ms} | 100 | 20 | Pause between cycles |
shimmer-invert | — | — | Use a contrasting highlight color |
All utilities are inheritable — set on a parent to affect all shimmer children.
Speed and Track Width#
Speed controls how fast the shimmer moves in pixels per second. --shimmer-track-width tells the animation how far the highlight travels for timing calculations; shimmer-container sets it from the container.
<span class="shimmer shimmer-speed-200 [--shimmer-track-width:400px] text-foreground/40">
Fast, wide shimmer
</span>
Color#
Use any Tailwind color with optional opacity:
<span class="shimmer shimmer-color-blue-500 text-blue-500/40">
Blue shimmer
</span>
<div class="shimmer shimmer-bg shimmer-color-blue-300/30 bg-muted h-4 rounded" />
Angle#
Control the sweep angle. The default is 15deg.
<div class="shimmer-container shimmer-angle-15">
<div class="shimmer shimmer-bg bg-muted h-4 w-full rounded" />
</div>
Position Hints (Angled Shimmer)#
For angled shimmers, set the unitless --shimmer-x and --shimmer-y position hints to sync elements:
<div class="shimmer-container shimmer-angle-15 flex gap-3">
<div class="shimmer shimmer-bg [--shimmer-x:20] [--shimmer-y:20] bg-muted size-12 rounded-full" />
<div class="flex-1 space-y-2">
<div class="shimmer shimmer-bg [--shimmer-x:52] [--shimmer-y:0] bg-muted h-4 w-24 rounded" />
<div class="shimmer shimmer-bg [--shimmer-x:52] [--shimmer-y:24] bg-muted h-4 w-full rounded" />
</div>
</div>
Repeat Delay#
Control the pause between animation cycles. The default is derived from speed: 100ms for text and 20ms for backgrounds.
<!-- Continuous shimmer, no pause -->
<span class="shimmer shimmer-repeat-delay-0 text-foreground/40">
Always moving
</span>
<!-- Long pause between cycles -->
<span class="shimmer shimmer-repeat-delay-3000 text-foreground/40">
Slow pulse
</span>
CSS Variables#
All values can be set via CSS variables for dynamic control:
| Variable | Description |
|---|---|
--shimmer-speed | Speed in px/s |
--shimmer-track-width | Track width for timing |
--shimmer-track-height | Track height for sizing |
--shimmer-spread | Highlight width |
--shimmer-angle | Sweep angle |
--shimmer-color | Highlight color |
--shimmer-duration | Override duration (ms) |
--shimmer-repeat-delay | Pause between cycles (ms) |
--shimmer-x | Horizontal position hint (unitless, px) |
--shimmer-y | Vertical position hint (unitless, px) |
<div
style={
{ "--shimmer-track-width": `${containerWidth}px` } as React.CSSProperties
}
<div className="shimmer shimmer-bg bg-muted h-4 w-full rounded" />
</div>
Browser Support#
Uses modern CSS features: @property, relative oklch(), color-mix(), tan(), independent translate, CSS masking, and CSS Container Queries. The compositor path is gated by -webkit-mask-clip: text; browsers that fail that gate keep the gradient fallback.
Supported: Chrome 119+, Firefox 128+, Safari 16.4+
Older browsers degrade gracefully. Below the floor the text shimmer drops its clip, gradient, and animation together, so the element renders as plain static text; shimmer-bg renders as a static block.