Original by Plinth · MIT
Loaders
Wave Bars
Seven bars pulsing on staggered phases, so the row reads as a travelling wave rather than a flicker.
#loader#wave#bars#css#animation
Install
npx shadcn@latest add https://plinthui.com/r/wave-bars.json// No hooks or handlers, so this stays a server component — it is pure CSS.
// Keyframes and per-child animation delays are outside what Tailwind can
// express, so they live in the style element below.
const css = `
.wave {
display: flex;
align-items: center;
gap: 7px;
/* Bars scale from their centre, so the row stays optically balanced instead
of growing downward off the baseline. */
height: 76px;
}
.wave__bar {
width: 9px;
height: 100%;
border-radius: 999px;
background: linear-gradient(180deg, #ffca7a, #ff5f9e);
transform-origin: center;
/* scaleY rather than height: it composites, so seven of these cost the
browser nothing, while an animated height relayouts every frame. */
animation: wave-pulse 1.05s ease-in-out infinite;
}
/* A phase offset per bar is the whole effect — same animation, staggered
start, and the row reads as a travelling wave rather than a flicker. */
.wave__bar:nth-child(1) { animation-delay: -0.98s; }
.wave__bar:nth-child(2) { animation-delay: -0.84s; }
.wave__bar:nth-child(3) { animation-delay: -0.70s; }
.wave__bar:nth-child(4) { animation-delay: -0.56s; }
.wave__bar:nth-child(5) { animation-delay: -0.42s; }
.wave__bar:nth-child(6) { animation-delay: -0.28s; }
.wave__bar:nth-child(7) { animation-delay: -0.14s; }
@keyframes wave-pulse {
0%, 100% { transform: scaleY(0.22); opacity: 0.45; }
50% { transform: scaleY(1); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
.wave__bar { animation: none; transform: scaleY(0.6); opacity: 0.8; }
}
`;
export function WaveBars({ bars = 7 }: { bars?: number }) {
return (
<>
<style>{css}</style>
{/* One status announcement for the whole group; the bars are decoration. */}
<div className="wave" role="status" aria-label="Loading">
{Array.from({ length: bars }, (_, i) => (
<span className="wave__bar" key={i} />
))}
</div>
</>
);
}