Text✨ Premium

Wave Text

Pure CSS animation: each letter bounces vertically with a staggered 0.08 s delay — a continuous wave with zero JavaScript.

CSSAnimationWave

You'll get access to the interactive demo with a free account.

This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Text has 56 effects, including 4 free. Explore the category →

3 usage examples

Wave headline — Agency or portfolio home page — Wave Text example 1

① Wave headline — Agency or portfolio home page

WhenThe main headline on a landing page or portfolio, displayed on load against a dark background, with no competing visual elements.
WhyThe wave draws the eye without overwhelming the message — the 18 px oscillation is noticeable from a distance yet subtle enough to keep text fully legible.
Settingsfont-size 4 rem, font-weight 900, animation-duration 1.8 s, delay 0.08 s per letter, primary brand color.
Processing indicator — Animated text during computation — Wave Text example 2

② Processing indicator — Animated text during computation

WhenAn in-progress operation (data analysis, AI model inference, report generation): the word ANALYSING ripples letter by letter for as long as the task is running, replacing a traditional spinner.
WhyThe wave's infinite, continuous motion is precisely what this effect does best — it communicates 'working' without an image or icon, in a textual and memorable way. No other CSS text effect holds this role as well.
Settingsfont-size 2.8 rem, font-weight 700, animation-duration 1.6 s, delay 0.08 s per letter, brand cyan color on dark background — stop via animation-play-state: paused when the task completes.
Announcement banner — New feature or live promotion — Wave Text example 3

③ Announcement banner — New feature or live promotion

WhenA full-width bar at the top of a shop or app, displaying a short word ("NEW", "SALE", "BETA") with the wave animation.
WhyThe undulating motion on 2–3 words draws attention without an image or video — the banner stays lightweight and does not block page rendering.
Settingsfont-size 3 rem, animation-duration 1.6 s, delay 0.06 s to amplify the effect on short words, contrasting color on accent background.

How it works

The animation is driven by @keyframes ktWave: from translateY(0) to translateY(-18px) and back, over 2 s with an ease-in-out infinite loop. Each <span class="char"> carries the CSS custom property --delay — 0 s for the first character, +0.08 s per subsequent one — assigned to animation-delay via .kt-wave .char { animation-delay: var(--delay) }. This offset produces the left-to-right wave propagation.

Each .char is display: inline-block — essential because translateY has no effect on inline elements. The user-select: none property on .kt-text prevents accidental selection of the animated text. No canvas or JS renderer is involved: the browser handles the animation entirely on the compositor layer.

Splitting text into spans is either pre-generated in the HTML (the static approach used in the supplied code) or produced dynamically by splitChars(el), which reads data-text or the element's textContent. Note: splitChars does not set --delay values — those must be added via CSS or a supplementary script after the call.

The CSS file bundles several ready-to-use variants beyond .kt-wave: .kt-elastic (one-shot entrance with elastic squash-and-stretch), .kt-scatter / .kt-whirlpool (assembly from scattered positions), .kt-magnetic (cursor repulsion), .kt-liquid (per-letter hover deformation), and .kt-explosion (click to explode). The active variant is selected by the second class on .kt-text — no keyframe changes needed.

Accessibility

  • prefers-reduced-motion absent: the code has no @media (prefers-reduced-motion: reduce) rule — the infinite animation runs even when the OS requests motion reduction. Recommendation: add @media (prefers-reduced-motion: reduce) { .kt-wave .char { animation: none; } }.
  • Individual .char elements carry no aria-hidden — screen readers spell out each letter separately (incoherent output). Recommendation: add role="img" aria-label="[full text]" on the .kt-text container and aria-hidden="true" on each .char.
  • White text (#fff) on dark background (#0a0a0f) exceeds an 18:1 contrast ratio — meets WCAG AA and AAA.
  • user-select: none is set on .kt-text: text cannot be selected or copied. Acceptable for decorative text; remove it if the text conveys information.
  • The .kt-wave animation requires no keyboard or mouse interaction — no focus trap is introduced, keyboard navigation passes freely through the component.

Browser compatibility

Relies on CSS animations and CSS custom properties (var(--delay)) — stable in all modern browsers since 2017.

Chrome 49+✓ Full
Firefox 31+✓ Full
Safari 9.1+✓ Full
Edge 15+✓ Full
Mobile iOS✓ Full
Android Chrome✓ Full

If CSS custom properties are not supported, all characters animate in sync (simultaneous bounce). Text stays readable and no JS error is thrown.

The code

HTML structure to paste into your page (CSS + JS available with a premium account):

index.html — structure
<div class="demo-preview">
  <div class="kt-text kt-wave" id="wave1" data-text="WAVE EFFECT"
       role="img" aria-label="Wave Effect">
    <!-- One &lt;span class="char"&gt; per character, --delay incremented by 0.08 s -->
    <span class="char" data-char="W" style="--delay: 0s;" aria-hidden="true">W</span>
    <span class="char" data-char="A" style="--delay: 0.08s;" aria-hidden="true">A</span>
    <span class="char" data-char="V" style="--delay: 0.16s;" aria-hidden="true">V</span>
    <span class="char" data-char="E" style="--delay: 0.24s;" aria-hidden="true">E</span>
    <!-- … -->
  </div>
</div>
🔒 Unlock the full code — from €2.99 the first month

Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
animation-duration (.kt-wave .char) 2s Duration of one wave cycle. Drop to 1.2 s for a lively wave; raise to 3 s for slow, fluid motion.
translateY in @keyframes ktWave (50%) -18px Vertical bounce amplitude. Increase to -30 px for a pronounced wave, reduce to -8 px for a subtle effect.
--delay (per .char) 0.08 s per letter Timing offset between letters. A larger gap (0.15 s) stretches the wave; a smaller gap (0.04 s) tightens it.
animation-timing-function (.kt-wave .char) ease-in-out Motion curve. cubic-bezier(.45, 0, .55, 1) gives a more elastic wave; linear produces a steady mechanical roll.
font-size (.kt-text) 2.2rem Text size. Use 4–5 rem for a hero, 1.6 rem for a badge or label.
color (.kt-text) #fff Letter color. Combine background: linear-gradient(…) + -webkit-background-clip: text + color: transparent for gradient text.
letter-spacing (.kt-text) 0.02em Inter-letter spacing. Increase to 0.15 em for a poster look; reduce to -0.02 em for compact text.
Active variant (.kt-text) .kt-wave Replace .kt-wave with .kt-elastic, .kt-liquid, or .kt-explosion to switch animation without touching keyframes.

FAQ

No — the .kt-wave animation is 100% CSS. If the HTML is pre-built with <span class="char"> elements and inline --delay values, zero JS is needed. The splitChars() function is an optional helper to generate those spans dynamically; it does not set delays (add them via CSS or a supplementary script).
Edit the translateY value in @keyframes ktWave at 50% — currently -18px. Increase the absolute value for a higher wave, reduce it for a subtler effect. You can also make amplitude vary per letter by substituting a CSS custom property --amp for the fixed value in the keyframe.
Yes — modern browsers automatically suspend CSS animations when the tab loses visibility (native behaviour since Chrome 57 and Firefox 51). No visibilitychange listener or IntersectionObserver is needed to save CPU.