Components
Equalizer bars
The small bouncing bars that mark what is playing in a list of tracks. Each bar keeps its own tempo so they never line up, and pausing settles them low and still.
pnpm dlx shadcn@latest add @caliper/equalizer-barsWhen to use
Use it for
- The playing row of a playlist, a podcast queue or a call's active speaker.
- Next to a live audio source, so people can find it at a glance.
Skip it when
- Showing real levels; the bars are a sign, not a meter.
- Several at once on one screen; only one thing plays.
Decisions
Why it is built this way, so you know what to keep when you change your copy.
Behaviour
- Uneven tempos
- Each bar has its own duration and offset, so the group never falls into step and looks alive.
- Named by state
- It is an image called "Playing" or "Paused"; pass an empty label when the row says it already.
Installation
pnpm dlx shadcn@latest add @caliper/equalizer-bars
Adds its 2 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.
Usage
This is the code behind the preview above.
import { useState, type ReactNode } from "react" import { EqualizerBars } from "@/components/ui/equalizer-bars" function Panel({ title, context, className = "max-w-md", children }: { title: string; context: string; className?: string; children: ReactNode }) { return ( <section className={`grid w-full gap-5 rounded-overlay border border-border bg-background p-6 shadow-resting max-sm:p-4 ${className}`}> <div className="grid gap-1"> <p className="font-medium">{title}</p> <p className="text-sm text-muted-foreground">{context}</p> </div> {children} </section> ); } const TRACKS = [ { title: "Low Tide", artist: "Harbour Lights", time: "3:41" }, { title: "Paper Kites", artist: "Mira Sol", time: "4:02" }, { title: "Night Ferry", artist: "Harbour Lights", time: "2:58" }, { title: "Glasshouse", artist: "Otto Vale", time: "3:17" }, ]; export function EqualizerBarsDemo() { const [playing, setPlaying] = useState<{ index: number; on: boolean }>({ index: 1, on: true }); return ( <Panel title="Late swims" context="Press a track to play it, again to pause."> <ol className="grid"> {TRACKS.map((track, index) => { const current = playing.index === index; return ( <li key={track.title}> <button type="button" aria-pressed={current && playing.on} className="flex w-full items-center gap-3 rounded-control px-2 py-2 text-left text-sm outline-none hover:bg-muted focus-visible:outline-2 focus-visible:outline-ring" onClick={() => setPlaying(current ? { index, on: !playing.on } : { index, on: true })} > <span className="grid w-5 place-items-center tabular-nums text-muted-foreground">{current ? <EqualizerBars playing={playing.on} label="" /> : index + 1}</span> <span className="min-w-0 flex-1"> <span className={`block truncate font-medium ${current ? "text-accent-text" : ""}`}>{track.title}</span> <span className="block truncate text-xs text-muted-foreground">{track.artist}</span> </span> <span className="text-xs tabular-nums text-muted-foreground">{track.time}</span> </button> </li> ); })} </ol> </Panel> ); }
API reference
EqualizerBars
component
Also accepts the native props of its root element and a ref.
Accessibility
It is built on native elements, so roles and focus come from the browser.
No custom key handling; Tab and the native activation keys do the work.
Motion
It declares no animation in its component source. Any transition comes from its stylesheet, which uses the duration tokens that collapse to instant under prefers-reduced-motion.
Related
- Media playerAn audio or video player with accessible controls and a progress bar.
- Live indicatorWhether a feed is live, in words and a dot. The dot sends out a soft ring only while live, blinks while reconnecting and rests otherwise, and gives one extra ring for each update you tell it about.
- LoaderA small indeterminate indicator in four shapes.