20 CSS Gradient Text Designs 04 / 20
CSS Rainbow Wave Per-Letter Gradient Text
Each letter bounces on its own phase-offset translateY wave while carrying a unique hue-stop pair, producing a travelling rainbow wave entirely in CSS.
The code
<div class="gt-04">
<span class="gt-04__label">Rainbow wave per-letter</span>
<div class="gt-04__wave-wrap" aria-label="RAINBOW">
<span class="gt-04__letter">R</span>
<span class="gt-04__letter">A</span>
<span class="gt-04__letter">I</span>
<span class="gt-04__letter">N</span>
<span class="gt-04__letter">B</span>
<span class="gt-04__letter">O</span>
<span class="gt-04__letter">W</span>
</div>
<p class="gt-04__subtitle">Per-letter hue offset animation</p>
<div class="gt-04__bar"></div>
</div> <div class="gt-04">
<span class="gt-04__label">Rainbow wave per-letter</span>
<div class="gt-04__wave-wrap" aria-label="RAINBOW">
<span class="gt-04__letter">R</span>
<span class="gt-04__letter">A</span>
<span class="gt-04__letter">I</span>
<span class="gt-04__letter">N</span>
<span class="gt-04__letter">B</span>
<span class="gt-04__letter">O</span>
<span class="gt-04__letter">W</span>
</div>
<p class="gt-04__subtitle">Per-letter hue offset animation</p>
<div class="gt-04__bar"></div>
</div>.gt-04, .gt-04 *, .gt-04 *::before, .gt-04 *::after {
margin: 0; padding: 0; box-sizing: border-box;
}
.gt-04 {
--bg: #f8f4f0;
font-family: 'DM Sans', sans-serif;
background: var(--bg);
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2.5rem;
padding: 3rem 2rem;
}
.gt-04__label {
font-size: .7rem;
letter-spacing: .2em;
text-transform: uppercase;
color: #bbb;
}
.gt-04__wave-wrap {
display: flex;
gap: 0;
}
.gt-04__letter {
font-size: clamp(3rem, 11vw, 7.5rem);
font-weight: 900;
line-height: 1;
display: inline-block;
background: linear-gradient(180deg,
hsl(calc(var(--hue) + 0deg), 90%, 55%) 0%,
hsl(calc(var(--hue) + 60deg), 90%, 45%) 100%
);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
animation: gt-04-wave 2.4s ease-in-out infinite;
}
/* stagger each letter's hue + delay */
.gt-04__letter:nth-child(1) { --hue: 0deg; animation-delay: 0s; }
.gt-04__letter:nth-child(2) { --hue: 30deg; animation-delay: -.2s; }
.gt-04__letter:nth-child(3) { --hue: 60deg; animation-delay: -.4s; }
.gt-04__letter:nth-child(4) { --hue: 90deg; animation-delay: -.6s; }
.gt-04__letter:nth-child(5) { --hue: 120deg; animation-delay: -.8s; }
.gt-04__letter:nth-child(6) { --hue: 150deg; animation-delay: -1s; }
.gt-04__letter:nth-child(7) { --hue: 180deg; animation-delay: -1.2s; }
.gt-04__subtitle {
font-size: clamp(1rem, 2.5vw, 1.3rem);
font-weight: 700;
color: #ccc;
letter-spacing: .1em;
}
.gt-04__bar {
width: min(500px, 80vw);
height: 6px;
border-radius: 3px;
background: linear-gradient(90deg,
#ff595e, #ffca3a, #6a4c93, #1982c4, #8ac926, #ff595e);
background-size: 200% 100%;
animation: gt-04-barscroll 3s linear infinite;
}
@keyframes gt-04-wave {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-14px); }
}
@keyframes gt-04-barscroll {
0% { background-position: 0% center; }
100% { background-position: 200% center; }
}
@media (prefers-reduced-motion: reduce) {
.gt-04__letter, .gt-04__bar { animation: none; transform: none; }
} .gt-04, .gt-04 *, .gt-04 *::before, .gt-04 *::after {
margin: 0; padding: 0; box-sizing: border-box;
}
.gt-04 {
--bg: #f8f4f0;
font-family: 'DM Sans', sans-serif;
background: var(--bg);
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2.5rem;
padding: 3rem 2rem;
}
.gt-04__label {
font-size: .7rem;
letter-spacing: .2em;
text-transform: uppercase;
color: #bbb;
}
.gt-04__wave-wrap {
display: flex;
gap: 0;
}
.gt-04__letter {
font-size: clamp(3rem, 11vw, 7.5rem);
font-weight: 900;
line-height: 1;
display: inline-block;
background: linear-gradient(180deg,
hsl(calc(var(--hue) + 0deg), 90%, 55%) 0%,
hsl(calc(var(--hue) + 60deg), 90%, 45%) 100%
);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
animation: gt-04-wave 2.4s ease-in-out infinite;
}
/* stagger each letter's hue + delay */
.gt-04__letter:nth-child(1) { --hue: 0deg; animation-delay: 0s; }
.gt-04__letter:nth-child(2) { --hue: 30deg; animation-delay: -.2s; }
.gt-04__letter:nth-child(3) { --hue: 60deg; animation-delay: -.4s; }
.gt-04__letter:nth-child(4) { --hue: 90deg; animation-delay: -.6s; }
.gt-04__letter:nth-child(5) { --hue: 120deg; animation-delay: -.8s; }
.gt-04__letter:nth-child(6) { --hue: 150deg; animation-delay: -1s; }
.gt-04__letter:nth-child(7) { --hue: 180deg; animation-delay: -1.2s; }
.gt-04__subtitle {
font-size: clamp(1rem, 2.5vw, 1.3rem);
font-weight: 700;
color: #ccc;
letter-spacing: .1em;
}
.gt-04__bar {
width: min(500px, 80vw);
height: 6px;
border-radius: 3px;
background: linear-gradient(90deg,
#ff595e, #ffca3a, #6a4c93, #1982c4, #8ac926, #ff595e);
background-size: 200% 100%;
animation: gt-04-barscroll 3s linear infinite;
}
@keyframes gt-04-wave {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-14px); }
}
@keyframes gt-04-barscroll {
0% { background-position: 0% center; }
100% { background-position: 200% center; }
}
@media (prefers-reduced-motion: reduce) {
.gt-04__letter, .gt-04__bar { animation: none; transform: none; }
}How this works
Every letter is wrapped in a span.gt-04__letter with a scoped --hue custom property. The gradient on each letter reads linear-gradient(180deg, hsl(calc(var(--hue) + 0deg),...), hsl(calc(var(--hue) + 60deg),...)), giving each character its own two-stop vertical gradient in the hue neighbourhood defined by --hue. Staggered animation-delay on the shared gt-04-wave keyframe produces the bouncing chase.
The wave keyframe only animates transform: translateY, which the compositor handles entirely off the main thread. Gradient painting is static — the hue shift is encoded at stylesheet parse time via --hue, so there is zero per-frame recalculation.
Customize
- Adjust the hue ladder by changing the
30degincrement on eachnth-child; a60degstep gives a more spread rainbow,15dega tighter analogous palette. - Change wave height by editing
translateY(-14px)in the keyframe — larger values give a more dramatic bounce, smaller values a subtle ripple. - Add a
text-shadowto the.gt-04__wave-wrapparent to cast a coloured drop shadow that follows the gradient without per-letter shadow logic.
Watch out for
- Each letter must be a separate DOM element for per-letter delay; wrapping words in a single
spanand relying on letter-spacing will not allow individual animation phases. - Accessibility: wrap the letter spans in a parent element with
aria-labelcontaining the full word so screen readers announce it as one token rather than spelling each character. hsl()withcalc()insidelinear-gradientis not supported in IE11 — use pre-computed hex colours for legacy targets.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 57+ | 10.1+ | 72+ | 57+ |
CSS custom properties inside hsl() calc() require Firefox 72+; earlier versions need hard-coded gradient colours.