Skip to content
caliper ui docs

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.

Free
pnpm dlx shadcn@latest add @caliper/equalizer-bars

Late swims

Press a track to play it, again to pause.

Installs components/ui/equalizer-bars.tsxDepends on none beyond ReactMotion none in component source

When 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

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

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

PropTypeDefaultDescription
playingbooleantrueBars bounce while true and settle low when false.
bars3 | 4 | 54
labelstring—Read instead of "Playing" or "Paused". Pass an empty string when the row already says it.

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.

All Changing values examples