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.
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



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 becomesmdWaveFade, 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. Putaria-hidden="true"on.md-waveand write the state as text next to it, in arole="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
#6366f1measures 4.4:1 on the#0a0a0fbackground, 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.
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):
<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>
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 / property | Default | Effect |
|---|---|---|
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
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.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.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.