25 CSS Text Animations 07 / 25
CSS Wave Text Animation
A sinusoidal wave ripples through individual letters using staggered translateY keyframes, each letter becoming a wave oscillator.
The code
<div class="ta-07">
<div class="ta-07__stage">
<p class="ta-07__label">Riding the wave</p>
<h2 class="ta-07__text">
<span style="--i:0">W</span><span style="--i:1">A</span><span style="--i:2">V</span><span
style="--i:3">E</span><span style="--i:4"> </span><span style="--i:5">T</span><span
style="--i:6">E</span><span style="--i:7">X</span><span style="--i:8">T</span>
</h2>
</div>
</div> <div class="ta-07">
<div class="ta-07__stage">
<p class="ta-07__label">Riding the wave</p>
<h2 class="ta-07__text">
<span style="--i:0">W</span><span style="--i:1">A</span><span style="--i:2">V</span><span
style="--i:3">E</span><span style="--i:4"> </span><span style="--i:5">T</span><span
style="--i:6">E</span><span style="--i:7">X</span><span style="--i:8">T</span>
</h2>
</div>
</div>.ta-07, .ta-07 *, .ta-07 *::before, .ta-07 *::after {
margin: 0; padding: 0; box-sizing: border-box;
}
.ta-07 ::selection { background: #0ea5e9; color: #fff; }
.ta-07 {
--bg: linear-gradient(135deg, #0c1a2e 0%, #0a0f1e 100%);
--wave: #38bdf8;
min-height: 100vh;
background: var(--bg);
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
font-family: 'Plus Jakarta Sans', 'Segoe UI', sans-serif;
}
.ta-07__stage { text-align: center; }
.ta-07__label {
font-size: 0.72rem;
letter-spacing: 0.2em;
text-transform: uppercase;
color: #1e4060;
margin-bottom: 0.75rem;
}
.ta-07__text {
font-size: clamp(2.2rem, 7vw, 4.5rem);
font-weight: 800;
color: var(--wave);
display: flex;
letter-spacing: 0.05em;
}
.ta-07__text span {
display: inline-block;
animation: ta-07-wave 1.2s ease-in-out infinite;
animation-delay: calc(var(--i) * 0.07s);
}
@keyframes ta-07-wave {
0%, 100% { transform: translateY(0); }
40% { transform: translateY(-14px); color: #fff; }
}
@media (prefers-reduced-motion: reduce) {
.ta-07__text span { animation: none; transform: none; }
} .ta-07, .ta-07 *, .ta-07 *::before, .ta-07 *::after {
margin: 0; padding: 0; box-sizing: border-box;
}
.ta-07 ::selection { background: #0ea5e9; color: #fff; }
.ta-07 {
--bg: linear-gradient(135deg, #0c1a2e 0%, #0a0f1e 100%);
--wave: #38bdf8;
min-height: 100vh;
background: var(--bg);
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
font-family: 'Plus Jakarta Sans', 'Segoe UI', sans-serif;
}
.ta-07__stage { text-align: center; }
.ta-07__label {
font-size: 0.72rem;
letter-spacing: 0.2em;
text-transform: uppercase;
color: #1e4060;
margin-bottom: 0.75rem;
}
.ta-07__text {
font-size: clamp(2.2rem, 7vw, 4.5rem);
font-weight: 800;
color: var(--wave);
display: flex;
letter-spacing: 0.05em;
}
.ta-07__text span {
display: inline-block;
animation: ta-07-wave 1.2s ease-in-out infinite;
animation-delay: calc(var(--i) * 0.07s);
}
@keyframes ta-07-wave {
0%, 100% { transform: translateY(0); }
40% { transform: translateY(-14px); color: #fff; }
}
@media (prefers-reduced-motion: reduce) {
.ta-07__text span { animation: none; transform: none; }
}How this works
Each character of the text is wrapped in its own <span> element with display: inline-block — this is essential because transform: translateY() has no effect on display: inline elements. All spans share the same ta-07-wave keyframe, which moves them from 0 to -12px up and back, but each span has a progressively larger animation-delay offset.
The sequential delay — typically 0.05s to 0.08s per letter — means each character is slightly behind the previous one in the animation cycle. When played together, this phase-offset creates the perception of a travelling wave even though every letter is running the same up-down animation independently. The wave speed is controlled by the base animation-duration and the delay increment together.
Customize
- Increase the wave amplitude by changing
translateY(-12px)totranslateY(-20px)for a more dramatic swell. - Speed up the ripple by reducing both
animation-duration(down to0.6s) and the per-letter delay increment (down to0.03s). - Change the wave character by using
ease-in-outfor a smooth sinusoid,cubic-bezier(0.34, 1.56, 0.64, 1)for a springy bounce, orsteps(4)for a robotic block move. - Add a colour wave by animating
colorthrough a gradient palette in the same keyframe — note this isn't GPU-composited so use sparingly. - Apply rotation per letter —
rotate(-8deg)at the peak — to create a more playful tumbling wave rather than a pure vertical oscillation.
Watch out for
- Inline text elements ignore
transform— each letter span must bedisplay: inline-blockor the wave has no visible effect. - Manually wrapping every character in a span is tedious; for production use a small JS character-split loop, but ensure a graceful no-JS fallback showing the full unsplit text.
- Very long words with tight per-letter delays can accumulate enough total delay that the last letter's animation starts seconds after the first, making the wave look broken.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 36+ | 9+ | 16+ | 36+ |
Fully supported in all modern browsers. The inline-block requirement is a behaviour, not a bug — always apply it.