Mischief

51 / Agent UI

Bar Visualizer

Frequency bars for audio on its way out, the counterpart to the trace a microphone draws on the way in.

Idle.

Installation

Copy the source into your project, or keep it behind a package.

npx shadcn@latest add Tinkerers-Labs/mischief-ui/bar-visualizer
import { BarVisualizer } from "mischief-ui/bar-visualizer"

Or paste it in yourself. The source imports the shared cn helper from @/lib/utils, so point that at your own copy.

registry/default/bar-visualizer/bar-visualizer.tsx
"use client" import * as React from "react" import {  RenderSurface,  useThemeColors,  type SurfaceColor,} from "@/registry/default/render-surface/render-surface"import { cn } from "@/lib/utils" export type BarVisualizerState = "idle" | "listening" | "speaking" export type BarVisualizerSource =

Usage

export function Speaking({ audio }) {
  return <BarVisualizer source={audio} state="speaking" />
}

What it can be given

A MediaStream for audio arriving, an audio or video element for audio playing, or an AnalyserNode you already built and would rather keep control of.

SourceWhen
MediaStreamA microphone, or a track from a call
HTMLMediaElementA reply being played back
AnalyserNodeYou already have a graph and want one tap

An element can be handed to createMediaElementSource exactly once, and a second attempt throws for the rest of the page's life. Development remounts every effect twice, so that tap is kept and reused rather than rebuilt, and the element goes back to the speakers when this component unmounts.

It says the state as well as drawing it

Bars that move are the whole point of this, which makes it useless under prefers-reduced-motion: a single painted frame would show a bar chart frozen mid-sentence, saying nothing about whether anything is happening.

So state is a prop rather than something inferred from the levels. It carries the answer in words through a live region in both cases, and where motion is reduced the bars fall back to an even resting row instead of a misleading still.

API

sourceMediaStream | HTMLMediaElement | AnalyserNode | nullThe audio to draw. Without one the bars rest.
state"idle" | "listening" | "speaking"What is happening, announced and put on the element.
barsnumberHow many bars to draw. Defaults to 24.
colorstringA theme token for the bars. Defaults to "--primary".
...rootPropsHTMLAttributes<HTMLDivElement>Native root attributes.

Accessibility

The state is announced through a polite live region in words, so a reader who cannot see the bars still learns that the assistant is listening or speaking. It is also on the element as data-state. Under prefers-reduced-motion the drawing is replaced by a still row of bars rather than a frozen frame, because a stopped visualiser reads as a stopped conversation.