9 CSS Text Shine Animation Effects — Copy-Paste
Each of these nine css text shine animation effects clips a moving gradient, a glow, or a spinning hue onto plain letters — one line, copy-paste, no image file
Auto-plays · click a tile to jump to its section · all nine in one zip
- 01 Flowing gradient text
- 02 Gradient angle move
- 03 Neon glow text
- 04 Shine sweep text
- 05 Glitch text
- 06 Chrome metal text
- 07 Flicker text
- 08 Outline glow
- 09 Rainbow text cycle
The order below follows what happens to the color, not how flashy an item looks. 01 and 02 move a gradient through the letters — sideways, then rotating. 03 and 04 use light rather than color to make a word stand out: a glow behind it, then a beam sweeping across it. 05 and 06 treat the letters as a surface that can misbehave or reflect — a broken scanline, then a chrome sheen. 07 and 08 work letter by letter, one flickering and one emptied out to a bare outline. 09 closes the set by spinning the entire hue wheel through a fixed rainbow. Every demo above sits in a small, non-scrollable iframe and loops on its own, since there is nothing in a 480×300 frame to click or hover. The heading text in each demo below is a placeholder — swap the string inside the <h1> for a real headline and every rule keeps working exactly as written.
01Flowing gradient text
A blue-to-cream gradient is clipped to the letters with background-clip: text, then background-position slides back and forth across a background-size set to 300% of the element's own width. Because the gradient is so much wider than the text, the visible slice keeps changing color as it scrolls, reading as color flowing through the letters rather than a gradient sitting still behind them.
.flow {
background: linear-gradient(90deg, $color, $subject-cream, $color, $subject-cream, $color);
background-size: 300% 100%;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
-webkit-text-fill-color: transparent;
animation: flow $duration $easing infinite;
}
@keyframes flow {
0% { background-position: 0% 50%; }
100% { background-position: 100% 50%; }
}
02Gradient angle move
@property registers --angle as a typed <angle> custom property, so the browser can interpolate it smoothly instead of snapping between values. That typed variable sits in linear-gradient()'s own angle slot and turns from 0deg to 360deg on a loop, which spins the gradient itself inside the letters rather than moving its position.
@property --angle {
syntax: "<angle>";
inherits: false;
initial-value: 0deg;
}
.spin {
--angle: 0deg;
background: linear-gradient(var(--angle), $color, $stage-orange, $color);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
animation: rotate-angle $duration $easing infinite;
}
@keyframes rotate-angle {
from { --angle: 0deg; }
to { --angle: 360deg; }
}
03Neon glow text
A second copy of the same letters sits behind the visible text via ::before, inherits the same gradient, and only ever gets filter: blur() applied to it. The blur radius and opacity swing between a dim and a bright step several times per loop, so the glow reads as breathing rather than a neon tube switching fully off and on.
.neon {
position: relative;
isolation: isolate;
background: linear-gradient(90deg, $color, $subject-cream);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.neon::before {
content: attr(data-text);
position: absolute;
inset: 0;
z-index: -1;
background: inherit;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
filter: blur(8px);
animation: breathe $duration linear infinite;
}
@keyframes breathe {
0%, 10% { filter: blur(4px); opacity: .28; }
15%, 25% { filter: blur(20px); opacity: 1; }
}
04Shine sweep text
The same word is stacked twice: the bottom copy stays a flat solid color, and only the top copy is clipped to a narrow, bright diagonal band with background-clip: text. background-position carries that band across the letters once per loop, so light appears to graze a metal surface rather than glow evenly.
.light {
background: linear-gradient(100deg, transparent 42%, rgba($stage-paper, .95) 50%, transparent 58%);
background-size: 260% 100%;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
animation: sweep $duration $easing infinite;
}
@keyframes sweep {
0% { background-position: 140% 50%; }
60% { background-position: -40% 50%; }
}
05Glitch text
Two color-shifted copies sit in mix-blend-mode: screen, each carrying a striped mask-image whose position jumps on steps() timing instead of easing, which breaks the letters into scanlines that snap rather than slide. A short filter: hue-rotate spike fires at the same instant to shift the color, with no clip-path slicing involved anywhere.
.r, .b {
position: absolute;
inset: 0;
mix-blend-mode: screen;
-webkit-mask-image: repeating-linear-gradient(to bottom, #000 0 3px, transparent 3px 7px);
mask-image: repeating-linear-gradient(to bottom, #000 0 3px, transparent 3px 7px);
animation: mask-jump-r $duration steps(1) infinite;
}
@keyframes mask-jump-r {
0%, 40%, 100% { -webkit-mask-position: 0 0; mask-position: 0 0; opacity: 0; }
45%, 55% { -webkit-mask-position: 0 3px; mask-position: 0 3px; opacity: .9; }
}
.tint {
animation: hue-jab $duration steps(1) infinite;
}
@keyframes hue-jab {
0%, 44%, 100% { filter: hue-rotate(0deg); }
47% { filter: hue-rotate(35deg); }
}
06Chrome metal text
Dark, white, and cream stops alternate at tight intervals inside one gradient, clipped to the letters the same way as item 01, but background-position here eases to a stop at the end of each loop instead of sliding at item 01's constant linear speed. The tight alternation is what sells the metal — a wide, soft gradient would just look like a colored glow instead of a reflective surface.
.chrome {
background: linear-gradient(
100deg,
$color 0%, $stage-paper 12%, $color 24%,
$subject-cream 40%, $stage-paper 52%, $color 64%,
$stage-paper 80%, $color 100%
);
background-size: 220% 100%;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
animation: reflect $duration $easing infinite;
}
@keyframes reflect {
0% { background-position: 0% 50%; }
100% { background-position: 100% 50%; }
}
07Flicker text
Each letter is wrapped in its own span, and a --i custom property multiplied inside animation-delay gives every letter a different starting point on the same short opacity keyframes. Because the delay lives on the letter, not a shared parent, the whole word never dims at once — it flickers letter by letter, closer to an old marquee sign than a single neon tube.
.marquee span {
display: inline-block;
color: $color;
animation: bulb $duration $easing infinite;
animation-delay: calc(var(--i, 0) * 120ms);
}
@keyframes bulb {
0%, 40%, 100% { opacity: 1; }
47% { opacity: .22; }
54% { opacity: 1; }
}
08Outline glow
color: transparent empties the letter fill entirely, -webkit-text-stroke draws a thin outline in its place, and filter: drop-shadow() is the only thing left animating — its radius widens and narrows so light spreads outward from the stroke and pulls back in. Nothing about the fill or the stroke width itself ever changes.
.outline {
color: transparent;
-webkit-text-stroke: 2px $color;
animation: glow $duration $easing infinite;
}
@keyframes glow {
0%, 100% { filter: drop-shadow(0 0 3px rgba($color, .5)); }
50% {
filter: drop-shadow(0 0 14px rgba($color, .9))
drop-shadow(0 0 26px rgba($color, .5));
}
}
09Rainbow text cycle
A fixed multicolor gradient is clipped to the letters and never moves — no background-position, no angle change. Instead filter: hue-rotate turns continuously from 0deg to 360deg, so the whole color wheel cycles through the same static band of letters rather than the gradient itself sliding anywhere.
.rainbow {
background: linear-gradient(90deg, $color, $stage-yellow, $stage-orange, $error, $subject-cream, $color);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
animation: hue-cycle $duration linear infinite;
}
@keyframes hue-cycle {
from { filter: hue-rotate(0deg); }
to { filter: hue-rotate(360deg); }
}
Where it breaks — prefixes, @property, and a glow that vanishes
Item 03's glow layer is the trap this set actually shipped with once: drop the isolation: isolate line from .neon and the ::before copy doesn't just look dimmer, it disappears completely. That ::before carries z-index: -1 so it paints behind the visible letters, but a negative z-index only stays inside its own parent's stacking context when that parent actually opens one. Without position: relative plus isolation: isolate on .neon, the pseudo-element's stacking context folds into whatever ancestor owns one first — here, the .stage backdrop — and the glow ends up painted below the entire demo background instead of just behind its own letters. MDN's isolation reference covers how a new stacking context keeps a layer's z-order scoped to its own parent.
The seven items built on background-clip: text (01, 02, 03, 04, 05, 06, and 09) still carry the -webkit- line for a real reason, not habit. Checking MDN's compatibility data for background-clip shows Chrome and Edge only accepted the unprefixed text value starting at version 120, while Safari picked it up earlier, at version 14 — before those versions, -webkit-background-clip: text was the only spelling either engine understood. Firefox has supported it unprefixed since version 49, with MDN's own compatibility notes flagging some edge cases around background-attachment: fixed that these nine demos never touch. Item 05's mask-image carries the same doubling for the same reason: MDN's compatibility data for mask-image puts unprefixed support at Chrome and Edge 120, Safari 15.4, and Firefox 53, so -webkit-mask-image and -webkit-mask-position stay next to the standard lines. Item 02's @property block asks for more: caniuse's @property table lists support from Chrome and Edge 85, Safari 16.4, and Firefox only as recently as version 128. On anything older, the <angle> custom property just fails to register, --angle stops interpolating between keyframes, and the gradient holds still at whatever angle it started instead of rotating — the text still reads fine, it just never spins. Every file in this set, unpacked and already measured after render, sits behind 5hzhmkcb typed exactly as it appears right here, nothing added and nothing trimmed off either end.
Accessibility
All nine turn off their looping animation under prefers-reduced-motion: reduce, and each settles on its own fixed value rather than one shared default. 01, 02, and 06 park their moving gradient or angle at a mid-cycle position — background-position: 40% 50% for 01 and 06, --angle: 45deg for 02 — chosen so the letters stay fully colored instead of sitting at either extreme. 04's shine band settles at background-position: 50% 50%, off to the side of its sweep, so no stray highlight freezes across the letters. 09 works differently: it resets hue-rotate all the way back to 0deg, the exact start of its loop, rather than stopping partway around the color wheel. 03 and 08 both settle on a fixed, clearly visible glow instead of their dimmest step — blur(10px) at .85 opacity for 03, a single 14px drop-shadow for 08 — so neither reads as broken or half-rendered. 05 takes the opposite approach: its two color-shifted mask layers drop straight to opacity: 0, so reduced motion doesn't freeze the glitch mid-jump, it removes the distortion entirely and leaves the plain base gradient underneath. 07's flicker stops outright and every letter locks to full opacity together, closing the one gap where reduced motion could otherwise leave part of a word invisible. Because 03 and 08 lean on filter: blur() or stacked drop-shadow() for contrast rather than a flat text color, pairing either with a plain, solid-color fallback matters more than usual for low-vision readers on a washed-out monitor. MDN's prefers-reduced-motion page documents the operating-system toggle behind that media query.
More CSS-only effects like these live on the CSS category hub, and what this site does and doesn't sell is on the about page.
FAQ
Do I still need the -webkit- prefix for a css text shine animation built on background-clip?
Yes, for now. Chrome and Edge only dropped the requirement at version 120 and Firefox has a handful of documented edge cases even where it works unprefixed, so leaving -webkit-background-clip: text next to the standard line costs nothing and keeps older installs from showing solid black text instead of a gradient.
Why did my copy of item 03's glow disappear instead of just looking dimmer?
isolation: isolate on .neon is doing real work there, not decoration — without it, the z-index: -1 glow layer stacks behind the demo's entire background rather than just behind its own letters. Add position: relative; isolation: isolate; to whatever element wraps the glowing text and the layer comes back.
Can several of these nine run on the same headline at once?
Technically, but it costs more than it's worth. Stacking background-clip: text with filter: blur() or a mask-image piles up extra layers the browser has to repaint every frame, and that shows up first as stutter on lower-end phones. One effect per headline holds up better than layering two or three for a busier look.