Wave Bars
Five 6 × 50 px bars in indigo (#6366f1), scaleY oscillating between .4 and 1 over a 1.2 s cycle, staggered 0.1 s left to right: the wave sweeps across all five bars in 0.4 s. Pure CSS, zero JavaScript.
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. Loaders has 29 effects, including 4 free. Explore the category →
3 usage examples



How it works
.loader-wave is a flex row (gap: 4px, align-items: center, height: 50px) of five empty span elements, each measuring 6 × 50 px and colored #6366f1. No element is created at runtime: the five bars are declared in the sold HTML. Measured in Chromium, the whole assembly spans 46 × 50 px (5 × 6 px bars + 4 × 4 px gaps). There is no JavaScript.
The @keyframes wave animation drives a scaleY from the vertical origin: 0% → scaleY(.4) (visual height 20 px), 20% → scaleY(1) (full height 50 px), back to scaleY(.4) at 40% and held through 100%. The rise and fall each take 240 ms; each bar stays at its minimum for 720 ms, or 60% of the 1.2 s cycle. Each span carries a negative delay decreasing by 0.1 s steps: −1.2 s / −1.1 s / −1.0 s / −0.9 s / −0.8 s. The height peak therefore travels bar by bar from left to right, producing a wave that sweeps across the five bars in 0.4 s, then repeats.
Measured in Chromium: at t ≈ 0, bar 1 reaches scaleY(0.986) (near full height) while bars 4 and 5 are at scaleY(0.4); at t ≈ 600 ms, bars 4 and 5 are at their maximum and bars 1 and 2 are at rest. No console errors. The animation runs continuously (animation-iteration-count: infinite) with no stop condition: the loader keeps running as long as it is in the DOM.
Accessibility
- prefers-reduced-motion not handled — measured: under Chromium's
prefers-reduced-motion: reduceemulation, all five bars keep animating (animationPlayState: running) with no change. Add@media (prefers-reduced-motion: reduce) { .loader-wave span { animation: none; transform: scaleY(.4); } }— bars stay visible at their minimum height (20 px). - No ARIA attributes:
.loader-wavecarries neitherrole="status", noraria-label, noraria-busy, noraria-live. Screen readers receive no indication that content is loading. Addrole="status" aria-label="Loading" aria-live="polite"to.loader-wave, or manage state viaaria-busy="true"on the parent region. - The five
spanelements are empty. Addaria-hidden="true"to the.loader-wavecontainer to exclude them from the accessibility tree: the text fromaria-labelwill then be the only content announced. - Contrast:
#6366f1on#0a0a0f(demo background) = 5.5:1; on white = 4.5:1 — both pass the 3:1 threshold for graphical elements. On a light grey background (#e5e7eb), the ratio drops to 2.1:1: switch to#4338ca(8.2:1 on white) or place a dark background behind the loader.
Browser compatibility
Pure CSS: requires @keyframes, transform: scaleY(), negative animation-delay and display: flex with gap. Zero dependencies, zero JavaScript. The limiting factor is gap on flexbox.
Without gap on flex (Chrome < 84, Safari < 14.1), the bars sit flush against each other: replace gap: 4px with margin-right: 4px on .loader-wave span and add .loader-wave span:last-child { margin-right: 0 }. Without CSS animations (IE 9), the five bars appear frozen at 50 px.
The code
HTML structure to paste into your page (CSS + JS available with a premium account):
<div class="loader-wave">
<span></span>
<span></span>
<!-- … 5 bars total —
each span has its animation-delay in the CSS -->
</div>
Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
| Bar count (HTML — spans + CSS — nth-child animation-delay) | 5 | Each added bar needs a new nth-child rule with a delay decremented by 0.1 s. A 6th bar gets animation-delay: −0.7 s. Reducing to 3 bars brings the loader down to 22 × 50 px. |
Zone height (CSS — .loader-wave { height: 50px }) |
50 px | Reference height for the bars before scaleY. The rendered minimum height is height × .4 (20 px), the maximum is this value. 32px for a button; 80px for a large audio display. |
Bar width (CSS — .loader-wave span { width: 6px }) |
6 px | Width of each bar, independent of height. 4px for a discreet indicator; 10px for a VU-meter-style display. |
Gap between bars (CSS — .loader-wave { gap: 4px }) |
4 px | 2px = nearly flush bars (dense equalizer look); 8px = well-spaced bars. |
Color (CSS — .loader-wave span { background: #6366f1 }) |
#6366f1 | Solid color for all five bars. For a vertical gradient, replace background with background: linear-gradient(to top, #6366f1, #a5b4fc). |
Cycle duration (CSS — animation: 1.2s … on the span elements) |
1.2 s | Duration of one full cycle. 0.8 s = fast wave; 2 s = slow rhythm. Changing the duration does not alter relative stagger between bars — the wave stays coherent. |
Stagger between bars (CSS — step between animation-delay values) |
0.1 s per bar (0.4 s total across 5 bars) | Delay between two consecutive bars. Wave travel time = (n − 1) × stagger. At 0.2 s, the wave takes 0.8 s to cross all 5 bars. Keep delays negative so the animation is already underway on page load. |
Amplitude (CSS — @keyframes wave: scaleY(.4) min, scaleY(1) max) |
scaleY .4 to 1 (20 px to 50 px) | Minimum and maximum height of each bar. scaleY(.2) = very thin bars at rest (10 px); scaleY(0) = bars vanish completely (they then look like vertical pulses appearing from nothing). |
FAQ
scaleY(.4), never to scaleY(0): on a height: 50px zone, the rendered minimum height is exactly 20 px (measured in Chromium). For bars that vanish completely, replace scaleY(.4) with scaleY(0) in the three keyframe positions where it appears (0%, 40%, and 100%) — the result looks more like vertical pulses shooting up and disappearing.span to the HTML and an nth-child rule in the CSS with a delay decremented by 0.1 s. For the nth bar (starting at 1): animation-delay: −(1.2 − (n − 1) × 0.1) s. For 6 bars, the 6th gets −0.7 s; for 7 bars, the 7th gets −0.6 s. For a large number of bars, reduce the stagger (e.g. 0.05 s) so the last bar still has a negative delay.requestAnimationFrame + two superimposed sinusoids), spanning the full width of its container. Wave Bars (fx-0426) is a set of five discrete bars, pure CSS, simulating an equalizer or VU-level display. Wave Form is an oscilloscope — a smooth, continuous waveform suited to an animated background; Wave Bars is a compact loading indicator (46 × 50 px), JavaScript-free, built for a UI component.