Bars Loader
Original · freeequalizer bars loader
byWeb InnoventixReact + Tailwind
loadbarsloaders
Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.
npx shadcn@latest add https://webinnoventix.com/r/load-bars.jsonload-bars.tsx
"use client";
import { useMemo, useState, type CSSProperties } from "react";
import { motion, useReducedMotion } from "motion/react";
type Accent = {
id: string;
label: string;
bar: string;
swatch: string;
solid: string;
ring: string;
accentColor: string;
glow: string;
};
const ACCENTS: Accent[] = [
{
id: "indigo",
label: "Indigo",
bar: "from-indigo-500 to-indigo-300 dark:from-indigo-400 dark:to-indigo-200",
swatch: "bg-indigo-500",
solid: "bg-indigo-600 text-white hover:bg-indigo-500 active:bg-indigo-700",
ring: "focus-visible:ring-indigo-500",
accentColor: "accent-indigo-500",
glow: "bg-indigo-400/30 dark:bg-indigo-500/25",
},
{
id: "violet",
label: "Violet",
bar: "from-violet-500 to-violet-300 dark:from-violet-400 dark:to-violet-200",
swatch: "bg-violet-500",
solid: "bg-violet-600 text-white hover:bg-violet-500 active:bg-violet-700",
ring: "focus-visible:ring-violet-500",
accentColor: "accent-violet-500",
glow: "bg-violet-400/30 dark:bg-violet-500/25",
},
{
id: "sky",
label: "Sky",
bar: "from-sky-500 to-sky-300 dark:from-sky-400 dark:to-sky-200",
swatch: "bg-sky-500",
solid: "bg-sky-600 text-white hover:bg-sky-500 active:bg-sky-700",
ring: "focus-visible:ring-sky-500",
accentColor: "accent-sky-500",
glow: "bg-sky-400/30 dark:bg-sky-500/25",
},
{
id: "emerald",
label: "Emerald",
bar: "from-emerald-500 to-emerald-300 dark:from-emerald-400 dark:to-emerald-200",
swatch: "bg-emerald-500",
solid: "bg-emerald-600 text-white hover:bg-emerald-500 active:bg-emerald-700",
ring: "focus-visible:ring-emerald-500",
accentColor: "accent-emerald-500",
glow: "bg-emerald-400/30 dark:bg-emerald-500/25",
},
{
id: "amber",
label: "Amber",
bar: "from-amber-500 to-amber-300 dark:from-amber-400 dark:to-amber-200",
swatch: "bg-amber-500",
solid:
"bg-amber-500 text-neutral-900 hover:bg-amber-400 active:bg-amber-600",
ring: "focus-visible:ring-amber-500",
accentColor: "accent-amber-500",
glow: "bg-amber-400/30 dark:bg-amber-500/25",
},
{
id: "rose",
label: "Rose",
bar: "from-rose-500 to-rose-300 dark:from-rose-400 dark:to-rose-200",
swatch: "bg-rose-500",
solid: "bg-rose-600 text-white hover:bg-rose-500 active:bg-rose-700",
ring: "focus-visible:ring-rose-500",
accentColor: "accent-rose-500",
glow: "bg-rose-400/30 dark:bg-rose-500/25",
},
];
const DENSITIES = [12, 18, 24, 32] as const;
/** Deterministic 0..1 pseudo-random so server and client render identically. */
function seededUnit(i: number): number {
const x = Math.sin(i * 12.9898 + 4.137) * 43758.5453;
return x - Math.floor(x);
}
export default function EqualizerBarsLoader() {
const reduce = useReducedMotion();
const [playing, setPlaying] = useState(true);
const [speed, setSpeed] = useState(1.4);
const [count, setCount] = useState<number>(24);
const [accentId, setAccentId] = useState<string>("indigo");
const accent = ACCENTS.find((a) => a.id === accentId) ?? ACCENTS[0];
const bars = useMemo(() => {
return Array.from({ length: count }, (_, i) => {
const p = seededUnit(i);
const q = seededUnit(i + 101);
const r = seededUnit(i + 211);
const peak = 0.32 + p * 0.63;
const duration = (720 + q * 620) / Math.max(speed, 0.1);
const delay = -(r * 900);
return { peak, duration, delay };
});
}, [count, speed]);
const running = playing && !reduce;
const statusText = reduce
? "Reduced motion is on. The loader is shown as a static waveform."
: playing
? `Loader running at ${speed.toFixed(1)}× speed with ${count} bars.`
: "Loader paused.";
const focusRing =
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-neutral-950";
return (
<section className="relative w-full overflow-hidden bg-gradient-to-b from-slate-50 to-white px-4 py-20 text-neutral-900 dark:from-neutral-950 dark:to-neutral-900 dark:text-neutral-100 sm:px-6 lg:py-28">
<style>{`
@keyframes eqbars-pulse {
0% { transform: scaleY(0.16); opacity: 0.55; }
100% { transform: scaleY(1); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
.eqbars-bar { animation: none !important; }
}
`}</style>
<div className="mx-auto max-w-4xl">
<motion.div
initial={reduce ? false : { opacity: 0, y: 16 }}
whileInView={reduce ? undefined : { opacity: 1, y: 0 }}
viewport={{ once: true, amount: 0.4 }}
transition={{ duration: 0.5, ease: [0.16, 1, 0.3, 1] }}
>
<header className="mb-8 text-center">
<p className="mb-3 inline-flex items-center gap-2 rounded-full border border-neutral-200 bg-white/70 px-3 py-1 text-xs font-medium uppercase tracking-widest text-neutral-500 dark:border-neutral-800 dark:bg-neutral-900/70 dark:text-neutral-400">
<span
className={`inline-block h-1.5 w-1.5 rounded-full ${accent.swatch}`}
aria-hidden="true"
/>
Loaders / Equalizer
</p>
<h2 className="text-balance text-3xl font-semibold tracking-tight sm:text-4xl">
Sound-wave loading indicator
</h2>
<p className="mx-auto mt-3 max-w-xl text-pretty text-sm leading-relaxed text-neutral-600 dark:text-neutral-400 sm:text-base">
An indeterminate loader that reads like a live audio equalizer.
Tune the tempo, density, and colour, then drop it into any waiting
state.
</p>
</header>
<div className="overflow-hidden rounded-3xl border border-neutral-200 bg-white/80 shadow-xl shadow-neutral-900/5 backdrop-blur dark:border-neutral-800 dark:bg-neutral-900/70 dark:shadow-black/30">
{/* Stage */}
<div className="relative flex flex-col items-center justify-center px-6 pb-8 pt-12 sm:px-12">
<div
className={`pointer-events-none absolute inset-x-0 top-1/3 mx-auto h-40 max-w-md rounded-full blur-3xl transition-colors duration-500 ${accent.glow}`}
aria-hidden="true"
/>
<div
role="img"
aria-label={`Equalizer-style loading animation with ${count} bars, currently ${
running ? "animating" : "paused"
}.`}
className="relative flex h-40 w-full max-w-xl items-end justify-center gap-[3px] sm:gap-1.5"
>
{bars.map((b, i) => {
const style: CSSProperties = {
height: `${(b.peak * 100).toFixed(2)}%`,
transformOrigin: "bottom",
animationName: reduce ? undefined : "eqbars-pulse",
animationDuration: `${b.duration.toFixed(0)}ms`,
animationDelay: `${b.delay.toFixed(0)}ms`,
animationTimingFunction: "cubic-bezier(0.36, 0, 0.66, 1)",
animationIterationCount: "infinite",
animationDirection: "alternate",
animationPlayState: running ? "running" : "paused",
};
return (
<span
key={i}
className={`eqbars-bar block min-w-[2px] flex-1 rounded-full bg-gradient-to-t ${accent.bar}`}
style={style}
/>
);
})}
</div>
<div
className="mt-6 h-px w-full max-w-xl bg-gradient-to-r from-transparent via-neutral-300 to-transparent dark:via-neutral-700"
aria-hidden="true"
/>
<p className="mt-4 flex items-center gap-2 text-sm font-medium text-neutral-600 dark:text-neutral-300">
<span
className={`relative inline-flex h-2 w-2 ${
running ? "" : "opacity-40"
}`}
aria-hidden="true"
>
{running && (
<span
className={`absolute inset-0 animate-ping rounded-full ${accent.swatch} opacity-75`}
/>
)}
<span
className={`relative inline-flex h-2 w-2 rounded-full ${accent.swatch}`}
/>
</span>
{reduce
? "Waveform (reduced motion)"
: playing
? "Streaming…"
: "Paused"}
</p>
<p aria-live="polite" role="status" className="sr-only">
{statusText}
</p>
</div>
{/* Controls */}
<div className="grid grid-cols-1 gap-px border-t border-neutral-200 bg-neutral-200 dark:border-neutral-800 dark:bg-neutral-800 sm:grid-cols-2">
{/* Playback */}
<fieldset className="bg-white p-6 dark:bg-neutral-900">
<legend className="mb-3 text-xs font-semibold uppercase tracking-widest text-neutral-500 dark:text-neutral-400">
Playback
</legend>
<button
type="button"
onClick={() => setPlaying((p) => !p)}
aria-pressed={playing}
className={`inline-flex w-full items-center justify-center gap-2 rounded-xl px-4 py-2.5 text-sm font-semibold shadow-sm transition ${accent.solid} ${accent.ring} ${focusRing}`}
>
{playing ? (
<svg
viewBox="0 0 24 24"
className="h-4 w-4"
fill="currentColor"
aria-hidden="true"
>
<rect x="6" y="5" width="4" height="14" rx="1" />
<rect x="14" y="5" width="4" height="14" rx="1" />
</svg>
) : (
<svg
viewBox="0 0 24 24"
className="h-4 w-4"
fill="currentColor"
aria-hidden="true"
>
<path d="M8 5.14v13.72a1 1 0 0 0 1.54.84l10.29-6.86a1 1 0 0 0 0-1.68L9.54 4.3A1 1 0 0 0 8 5.14Z" />
</svg>
)}
{playing ? "Pause" : "Play"}
</button>
<p className="mt-3 text-xs leading-relaxed text-neutral-500 dark:text-neutral-400">
{reduce
? "Your system prefers reduced motion, so the bars hold still."
: "Toggle the animation without unmounting the loader."}
</p>
</fieldset>
{/* Tempo */}
<div className="bg-white p-6 dark:bg-neutral-900">
<div className="mb-3 flex items-center justify-between">
<label
htmlFor="eqbars-tempo"
className="text-xs font-semibold uppercase tracking-widest text-neutral-500 dark:text-neutral-400"
>
Tempo
</label>
<span className="rounded-md bg-neutral-100 px-2 py-0.5 text-xs font-semibold tabular-nums text-neutral-700 dark:bg-neutral-800 dark:text-neutral-200">
{speed.toFixed(1)}×
</span>
</div>
<input
id="eqbars-tempo"
type="range"
min={0.5}
max={2.5}
step={0.1}
value={speed}
onChange={(e) => setSpeed(Number(e.target.value))}
aria-valuetext={`${speed.toFixed(1)} times speed`}
className={`h-2 w-full cursor-pointer rounded-full bg-neutral-200 dark:bg-neutral-700 ${accent.accentColor} ${accent.ring} ${focusRing}`}
/>
<div className="mt-2 flex justify-between text-[11px] font-medium text-neutral-400 dark:text-neutral-500">
<span>Slow</span>
<span>Fast</span>
</div>
</div>
{/* Density */}
<div className="bg-white p-6 dark:bg-neutral-900">
<p
id="eqbars-density-label"
className="mb-3 text-xs font-semibold uppercase tracking-widest text-neutral-500 dark:text-neutral-400"
>
Density
</p>
<div
role="radiogroup"
aria-labelledby="eqbars-density-label"
className="grid grid-cols-4 gap-2"
>
{DENSITIES.map((n) => (
<label key={n} className="relative block">
<input
type="radio"
name="eqbars-density"
value={n}
checked={count === n}
onChange={() => setCount(n)}
aria-label={`${n} bars`}
className="peer sr-only"
/>
<span
className={`flex h-10 items-center justify-center rounded-lg border border-neutral-200 bg-white text-sm font-semibold text-neutral-600 transition peer-hover:border-neutral-300 peer-checked:border-transparent peer-checked:bg-neutral-900 peer-checked:text-white peer-focus-visible:ring-2 peer-focus-visible:ring-offset-2 peer-focus-visible:ring-offset-white dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-300 dark:peer-checked:bg-white dark:peer-checked:text-neutral-900 dark:peer-focus-visible:ring-offset-neutral-950 ${accent.ring}`}
>
{n}
</span>
</label>
))}
</div>
</div>
{/* Colour */}
<div className="bg-white p-6 dark:bg-neutral-900">
<p
id="eqbars-colour-label"
className="mb-3 text-xs font-semibold uppercase tracking-widest text-neutral-500 dark:text-neutral-400"
>
Colour
</p>
<div
role="radiogroup"
aria-labelledby="eqbars-colour-label"
className="flex flex-wrap gap-3"
>
{ACCENTS.map((a) => (
<label
key={a.id}
className="relative block"
title={a.label}
>
<input
type="radio"
name="eqbars-colour"
value={a.id}
checked={accentId === a.id}
onChange={() => setAccentId(a.id)}
aria-label={a.label}
className="peer sr-only"
/>
<span
className={`block h-8 w-8 rounded-full ${a.swatch} ring-offset-2 ring-offset-white transition peer-checked:ring-2 peer-checked:ring-neutral-900 peer-focus-visible:ring-2 peer-focus-visible:ring-sky-500 dark:ring-offset-neutral-900 dark:peer-checked:ring-white`}
/>
</label>
))}
</div>
</div>
</div>
</div>
<p className="mt-6 text-center text-xs text-neutral-400 dark:text-neutral-500">
Pure CSS bars — no canvas, no images. Honours{" "}
<code className="rounded bg-neutral-100 px-1 py-0.5 font-mono text-[11px] text-neutral-600 dark:bg-neutral-800 dark:text-neutral-300">
prefers-reduced-motion
</code>
.
</p>
</motion.div>
</div>
</section>
);
}Dependencies
motion
Licence
Built by Web Innoventix. Free for personal and commercial use, no attribution required.
Built by Web Innoventix
Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.
Get a free quoteSimilar components
Browse all →
Spinner Set Loader
Originalset of circular spinners in sizes

Dots Loader
Originalbouncing dots loader

Ring Loader
Originalring spinner loader

Pulse Loader
Originalpulsing circle loader

Orbit Loader
Originalorbiting dots loader

Wave Loader
Originalwave loader

Bounce Loader
Originalbouncing ball loader

Gradient Ring Loader
Originalgradient conic ring spinner

Dual Ring Loader
Originaldual counter-rotating rings

Square Loader
Originalmorphing square loader

Typing Loader
Originaltyping indicator loader

Page Overlay Loader
Originalfull-page loading overlay with progress

