Text✨ Premium

Wave Animation

Each letter oscillates between font-weight 300 and 900 via a CSS keyframe, a 0.15 s per-character delay propagating a weight wave from left to right.

CSSWaveStagger

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

Audio player — Pulsing track title — Wave Animation example 1

① Audio player — Pulsing track title

WhenIn a web audio player or artist site, the current track title is displayed large — each letter oscillates in weight to the beat tempo, turning the typography into a passive audio visualisation.
WhyThe font-weight undulation conveys musical tempo with no canvas or Web Audio API — the CSS weight wave alone creates the impression that the text is responding to the music, reinforcing the visual identity of the displayed track.
Settingsfont-size 3–4 rem, per-letter delay 0.08 s (fast wave matching ~120 BPM), animation-duration 1 s to simulate a strong beat, peak colour matching the album art accent (e.g. #8b5cf6).
Online publication — Animated section heading — Wave Animation example 2

② Online publication — Animated section heading

WhenAt the opening of a section or article in a digital magazine, to visually signal a section change without a complex scroll-trigger setup.
WhyThe weight wave mimics a progressive typographic emphasis — appropriate in an editorial context where the animation stays within the text line and doesn't overflow.
Settingsfont-size 2–2.5 rem, delay 0.2 s per letter (slow, sweeping wave), peak colour matching the editorial brand palette (e.g. gold #f59e0b).
Motion design — Looping animated wordmark — Wave Animation example 3

③ Motion design — Looping animated wordmark

WhenIn a showreel, portfolio, or About page where the brand name is presented alone on a neutral background as a motion design piece.
WhyThe native CSS animation avoids any SVG or external library — the wordmark pulses autonomously, subtle enough to sit as a background element in a presentation screen or brand loader.
Settingsfont-size 3–3.5 rem, delay 0.08 s per letter (very fast, fluid wave), peak colour pure white (#fff) so weight alone carries the impact.

How it works

The animation relies on a single @keyframes vfWave CSS rule controlling three properties at once: font-weight moves from 300 (light) to 900 (black) at the peak, transform: translateY lifts the letter by 10 px at maximum weight, and color shifts from white at 60% opacity to violet #8b5cf6. Synchronising these three axes on a single animation gives the oscillation its sculptural quality.

Each letter is an independent <span class="vf-wave-char"> whose start offset is injected as an inline style (animation-delay: N × 0.15 s). This progressive offset is what creates the wave: all letters run the same 2 s cycle but start at staggered moments. An 8-letter word produces a total phase window of 1.05 s.

font-weight interpolation between 300 and 900 is smooth only with a variable font supporting the wght axis (e.g. Inter Variable, Roboto Flex, Plus Jakarta Sans). With a static font the browser jumps between available weights (400, 700…); the effect remains readable but less fluid. The declared family is Inter, sans-serif — without explicitly loading Inter Variable, the result depends on the local installation.

The JS field is empty: the effect is 100% CSS. There is no requestAnimationFrame, no IntersectionObserver, no public API — the animation starts as soon as the classes are in the DOM and loops indefinitely. To pause it, apply animation-play-state: paused to .vf-wave-char via JS, or remove the class.

Accessibility

  • prefers-reduced-motion not implemented: no @media (prefers-reduced-motion: reduce) rule is defined — the animation loops indefinitely regardless of OS preference. Add manually: @media (prefers-reduced-motion: reduce) { .vf-wave-char { animation: none; font-weight: 400; color: #fff; } }
  • The text is native HTML content (spans in the normal flow) — screen readers read the letters in order; no canvas or aria-label required.
  • The resting color rgba(255,255,255,0.6) on #0a0a0f meets WCAG AA for large text (≥ 18 pt); the peak color #8b5cf6 reaches a ratio ≈ 4.6:1 on a black background, passing AA for large text.
  • No interactive element in the default structure — no focus or keyboard handling required.
  • No role or aria-label is set by default. If the animated text is a page title, wrap .vf-wave-text in an <h1> or <h2> to stay within the semantic heading hierarchy.

Browser compatibility

Requires CSS @keyframes and animation — supported in all modern browsers for years. Smooth font-weight interpolation requires a variable font (axis wght); without it, weights snap to available values.

Chrome 103+✓ Full (native variable fonts)
Firefox 62+✓ Full
Safari 11+✓ Full
Edge 79+✓ Full
Mobile iOS 11+✓ Full
Android Chrome✓ Full

If the font is not variable, <code>font-weight</code> jumps between available weights instead of interpolating — the color effect and <code>translateY</code> remain active. No JS errors; the page stays functional.

The code

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

index.html — structure
<div class="vf-wave-scene">
  <div class="vf-wave-text" id="vfWaveText">
    <span class="vf-wave-char" style="animation-delay:0s;">V</span>
    <span class="vf-wave-char" style="animation-delay:0.15s;">O</span>
    <span class="vf-wave-char" style="animation-delay:0.30s;">T</span>
    <span class="vf-wave-char" style="animation-delay:0.45s;">R</span>
    <span class="vf-wave-char" style="animation-delay:0.60s;">E</span>
    <!-- one .vf-wave-char per letter — animation-delay +0.15 s per character -->
  </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-delay (HTML inline style) 0.15 s × index Start offset per letter. Reduce to 0.08 s for a fast wave, increase to 0.25 s for a slow dramatic sweep.
animation-duration (.vf-wave-char) 2s Full cycle duration per letter. 1 s = dynamic, 4 s = slow and cinematic.
font-weight 0%/100% (@keyframes vfWave) 300 Resting weight. 100 = ultralight, 400 = regular. Lower values increase contrast with the peak.
font-weight 50% (@keyframes vfWave) 900 Peak weight. 700 = subtle bold, 900 = maximum black. Only interpolates smoothly with a variable font.
translateY 50% (@keyframes vfWave) -10px Vertical lift at peak weight. 0px = pure weight change with no movement, 20px = noticeable bounce.
color 50% (@keyframes vfWave) #8b5cf6 Accent colour at full weight. Swap for your brand primary colour.
color 0%/100% (@keyframes vfWave) rgba(255,255,255,.6) Colour and opacity at rest. rgba(255,255,255,1) for always-fully-visible letters.
font-size (.vf-wave-char) 2.5rem Character size. 1.5 rem for a card title, up to 8 rem for a full-width hero. The variable font must have instances at this size.
font-family (.vf-wave-char) Inter, sans-serif Must be a variable font with a wght axis for smooth interpolation. Alternatives: Roboto Flex, Plus Jakarta Sans, Nunito Variable.

FAQ

Smooth font-weight interpolation requires a variable font with a wght axis. Without one (e.g. static Inter or Arial), the browser snaps between available weights (400 → 700) instead of interpolating. Load Inter Variable via Google Fonts (@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap')) to get the continuous gradient.
Create one <span class="vf-wave-char"> per letter inside your .vf-wave-text, starting with animation-delay: 0s on the first letter and adding 0.15 s per character. Spaces can remain as plain HTML text without the class — they will not be animated.
Yes: simply reverse the delays — give animation-delay: 0s to the last letter and increase towards the first. For a centre-out wave: delay 0 s on the middle letters, increasing towards both ends. No global CSS change is needed.