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.

Pure CSS MIT licensed
Live Demo Open in tab
Open in playground

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">&nbsp;</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; }
}

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) to translateY(-20px) for a more dramatic swell.
  • Speed up the ripple by reducing both animation-duration (down to 0.6s) and the per-letter delay increment (down to 0.03s).
  • Change the wave character by using ease-in-out for a smooth sinusoid, cubic-bezier(0.34, 1.56, 0.64, 1) for a springy bounce, or steps(4) for a robotic block move.
  • Add a colour wave by animating color through 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 be display: inline-block or 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

ChromeSafariFirefoxEdge
36+ 9+ 16+ 36+

Fully supported in all modern browsers. The inline-block requirement is a behaviour, not a bug — always apply it.

Search CodeFronts

Loading…