Description
Shimmer Text layers a moving gradient on top of a text element to signal an in-progress state: streaming output, “Generating response”, or any transient label that should feel alive without resorting to a spinner. The effect is built with two stacked layers. The base layer renders the text in a muted color. A ::before pseudo-element reads the same string from data-text, clips a transparent-to-highlight-to-transparent gradient to the glyph shapes via background-clip: text, then continuously sweeps that band from right to left. Because it is pure CSS with no state or timers, you can drop it on any element and it starts immediately.
Example
CSS
:root {
--shimmer-dur: 2000ms;
--shimmer-base: #7c7c7c;
--shimmer-highlight: #0d0d0d;
--shimmer-band: 400%;
--shimmer-ease: linear;
}
/* Two-layer construction:
1. The base text renders normally in --shimmer-base.
2. ::before duplicates it via content: attr(data-text),
paints a transparent → highlight → transparent gradient
onto it, and clips that gradient to the glyphs via
background-clip: text. Animating background-position
sweeps the band across the text. */
.t-shimmer {
position: relative;
display: inline-block;
color: var(--shimmer-base);
}
.t-shimmer::before {
content: attr(data-text);
position: absolute;
inset: 0;
pointer-events: none;
background-image: linear-gradient(
90deg,
transparent 0%,
transparent 40%,
var(--shimmer-highlight) 50%,
transparent 60%,
transparent 100%
);
background-size: var(--shimmer-band) 100%;
background-repeat: no-repeat;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
-webkit-text-fill-color: transparent;
animation: t-shimmer var(--shimmer-dur) var(--shimmer-ease) infinite;
}
@keyframes t-shimmer {
0% {
background-position: 100% 0;
}
100% {
background-position: 0% 0;
}
}
@media (prefers-reduced-motion: reduce) {
.t-shimmer::before {
animation: none !important;
}
}
React
Add the t-shimmer class and a data-text attribute to any <span>. The visible text content and the data-text value must always be identical because the ::before pseudo-element reads data-text to draw the highlight layer. If they drift out of sync the shimmer band will appear misaligned over the wrong characters.
import "./shimmer-text.css"; // paste the CSS above
export function StatusLabel({ label }: { label: string }) {
return (
<span className="t-shimmer" data-text={label}>
{label}
</span>
);
}
Variables
| Variable | Default | Notes |
|---|---|---|
--shimmer-dur | 2000ms | sourced from --p15-dur |
--shimmer-base | #7c7c7c | sourced from --p15-base |
--shimmer-highlight | #0d0d0d | sourced from --p15-highlight |
--shimmer-band | 400% | sourced from --p15-band |
--shimmer-ease | linear | sourced from --p15-ease |
Credit
Adapted from Shimmer Text on transitions.dev by Jakub Antalik. Original source: github.com/Jakubantalik/transitions.dev.