Transitions✨ Premium

Kinetic Wave

Twenty 10 px dots in a row that rise 30 px and come back down, each 80 ms after its neighbor: a wave running left to right in a 2 s loop, made of a single transform keyframe — pure CSS, zero JavaScript, a wave of light with no movement when the user reduces motion.

CSSJSVague

Updated

This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Transitions has 52 effects, including 9 free. Explore the category →

3 usage examples

Pairing a Bluetooth headset — a headphone maker's app — Kinetic Wave example 1

① Pairing a Bluetooth headset — a headphone maker's app

WhenThe user holds the headset button: under the headset drawing, the wave runs during the search; when the device answers, it fades out and gives way to "Headset connected".
WhyThe wave suggests a signal looking for its receiver; it stops when the device is found, which makes the arrival of the result readable with no extra animation.
SettingsLight blue dots (#60a5fa), original size and row (10 px, 276 px); delays shifted by −2 s for a wave formed from the start; in B, the row goes to opacity: 0 (the animation keeps running behind the scenes) and a 68 px green check appears; demo loop of 1.3 s searching, 1 s of result.
Measuring the swell — a surf club's app — Kinetic Wave example 2

② Measuring the swell — a surf club's app

WhenThe surfer asks for sea conditions: the wave runs while the app queries the offshore buoy, then swell height, period and direction show up with the recommended time slot.
WhyThe wave echoes the shape of the swell being measured: the wait makes sense to the surfer, and the wave "freezes" into figures when the reading comes in.
SettingsPale cyan dots (#a5f3fc) on an ocean-blue background; same keyframe (30 px, 2 s); result in three columns, 34 px figures; delays shifted by −2 s, same demo loop as example 1.
Searching for an avalanche beacon — a training app for avalanche transceivers — Kinetic Wave example 3

③ Searching for an avalanche beacon — a training app for avalanche transceivers

WhenDuring the drill, the transceiver screen shows the wave while no signal is picked up; as soon as the buried beacon answers, an arrow and the distance, "12 m", replace it.
WhyThe steady propagation reads as a sweep in progress, with no flashing figure; when it disappears, the eye goes straight to the distance.
SettingsOrange dots (#fb923c) on the black screen of a device rimmed in orange, original 276 px row; in B, a green arrow and the distance in 40 px; delays shifted by −2 s.

How it works

The .md-wave-stage frame (at least 300 px tall, background #0a0a0f) centers a .md-wave row of twenty .md-wave-dots: 10 px discs, indigo #6366f1, 4 px apart, 276 px long in all. They all play the same animation, 2s ease-in-out infinite mdWave: translateY(0) at start and end, translateY(-30px) halfway. What makes the wave is the delay written on each dot in the HTML: animation-delay of 0 s, 0.08 s, 0.16 s… up to 1.52 s for the twentieth. Two neighbors are 4% of the cycle apart; from one end of the row to the other, the gap reaches 76%: you always see about three quarters of a wave, whose crest moves from left to right.

Only transform is animated: the browser moves the dots without recalculating layout, and the row keeps its place in the page while the dots rise. The twenty dots are written in the HTML, with no script: what shows is exactly what you paste, and the wave works even with JavaScript turned off. Measured in Chromium 151, Firefox 153 and WebKit 26: 20 dots, a 276 px row, different vertical positions at every reading. Since the delays are positive, on load the wave forms from the left: the last dot only starts after 1.52 s. For a wave already formed when it appears, subtract 2 s from each delay (see the settings).

The wave is a loop by nature: it says "searching, listening" for as long as the wait lasts, and it is up to your page to remove it when the result arrives, as in the three examples. The frame has its own class, prefixed like the rest of the effect: two effects from the library pasted on the same page do not overwrite each other. A variant in the category makes the same wave with vertical bars whose height changes.

Accessibility

  • Reduced motion: under prefers-reduced-motion: reduce, the dots stop moving; the animation becomes mdWaveFade, an opacity change from 1 to 0.3 with the same delays. The wave still travels left to right, in light instead of height. Measured in Chromium 151, Firefox 153 and WebKit 26: a single vertical position recorded, changing opacity.
  • Screen readers: the dots are empty divs with no text. Put aria-hidden="true" on .md-wave and write the state as text next to it, in a role="status" element ("Looking for your headset…"): the text change is announced when the result arrives.
  • Stopping: motion lasting more than 5 seconds must be stoppable. Here the stop is the result: remove or hide the wave as soon as the search succeeds or fails, and plan a message if it drags on ("Still nothing? Bring the headset closer").
  • Contrast: indigo #6366f1 measures 4.4:1 on the #0a0a0f background, above the 3:1 threshold for a graphic element.

Browser compatibility

Pure CSS (@keyframes, transform, animation-delay), 1.1 KB of CSS and 1.4 KB of HTML, zero JavaScript. Measured in Chromium 151, Firefox 153 and WebKit 26: same wave, same opacity replacement under reduced motion.

Chrome 43+✓ Full
Firefox 16+✓ Full
Safari 9+✓ Full
Edge 12+✓ Full
Mobile iOS 9+✓ Full
Android Chrome 43+✓ Full

Without CSS animations, the row of twenty dots stays still, centered: the indicator shows, without a wave. Always add a status text next to it, which stays readable in every case.

The code

HTML structure to paste into your page (the CSS + JS come with Premium):

index.html — structure
<div class="md-wave-stage">
  <div class="md-wave">
    <div class="md-wave-dot" style="animation-delay: 0s;"></div>
    <div class="md-wave-dot" style="animation-delay: 0.08s;"></div>
    … (20 dots, delay + 0.08 s each time)
  </div>
</div>
Unlock the full code — $9.90/mo incl. tax

The full HTML + CSS + JS of Kinetic Wave, ready to paste — plus all 840 effects. Or $299 once, lifetime access.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Wave height (CSS — translateY(-30px) at 50%) 30 px 12 px gives a discreet indicator inside a line of text; 50 px a broad wave for a waiting screen. Leave room above the row: it does not grow with the wave.
Cycle duration (CSS — 2s) 2 s 1.2 s makes the search feverish, 3 s calm. Keep the gap between delays at about 4% of the duration (0.05 s for 1.2 s) to keep the same wave shape.
Gap between two dots (HTML — animation-delay) 0.08 s (0 → 1.52 s) 0.04 s stretches the wave (only half a crest shows); 0.2 s tightens it into several small waves. The shape depends on gap × number of dots / duration.
Wave formed on display (HTML — negative delays) positive delays: the wave forms from the left in 1.5 s Subtract the cycle duration from each delay (−2 s, −1.92 s … −0.48 s): same offset between dots, but each dot starts already moving. Useful when the wave appears on a click.
Number, size and color of dots (HTML + CSS — .md-wave-dot) 20 dots of 10 px, #6366f1, 4 px apart 12 dots of 14 px make a more readable wave on mobile (adjust the delays, still 0.08 s apart). A color gradient from dot to dot underlines the direction of travel.

FAQ

The simplest way is to hide a parent of the row (hidden attribute), or the row itself with a class that sets opacity to 0, and show the result in its place. The hidden attribute set on .md-wave is not enough: its display: flex wins; in that case add .md-wave[hidden] { display: none }. To freeze the wave where it is, set animation-play-state: paused on .md-wave-dot. If you hide it with display: none, the animation restarts from zero when it comes back and the wave forms again from the left; with opacity: 0, it keeps running behind the scenes and reappears already formed.
So the wave depends on no script: 20 lines of HTML, each with its delay, and it shows even without JavaScript or before it loads. For a very long row, you can generate the dots: a loop that creates a div.md-wave-dot and writes style.animationDelay = (i * 0.08) + 's' — then empty the row in the HTML so the dots are not doubled.
Because transform moves the element without touching layout: the browser does not have to recompute the position of other elements every frame, and the motion stays smooth even on a modest phone. Animating margin-top or height on twenty elements would force a relayout of the row sixty times per second.