Refined UI

An opinionated, customizable superset of shadcn/ui

AgentsPrimitivesSkillsSkins
Integration
Guides
  • Overview
  • Get started
  • Shadcn compatibility
  • Architecture
  • Agent surface
Agents
  • Action Bar
  • Audio Recorder
  • Audio VisualizerBetaBeta — the props contract is close to final, but small breaking changes can still land.
  • Chat Input
  • Chat Input Attachment
  • Chat Message
  • Chat Scene
  • Code Block Editor
  • Dynamic NotificationExpExperimental — the contract and the rendering can change without notice. Own the installed copy before shipping it.
  • Environment Variables
  • Inline Attachment
  • Markdown Block
  • Task List
  • Thread Rail
  • Tool Call
  • User Ask
Blocks
  • Chat
  • Theme toggle
  1. Agents
  2. AudioVisualizer
Agents

AudioVisualizer

BetaBeta — the props contract is close to final, but small breaking changes can still land.

Levels-driven realtime audio visualizer offered in two usage versions - bars and line - sharing one export and one props contract.

Version

Composition

The preferred shape for composing the installed agent from its exported parts.

Root

Single-slot surface with no nested parts.

AudioVisualizer

Usage versions are sibling registry items sharing one export and one props contract — install the Bars item with the command above; swapping versions later is an import-path change, no call site moves.

Registry command
npx shadcn@latest add http://127.0.0.1:3000/r/refined/audio-visualizer.json
See registry manifest

Usage

"use client";
​
import { useEffect, useState } from "react";
​
import { AudioVisualizer } from "@/components/refined-ui/audio-visualizer";
​
// Mastra voice sessions hand you the live MediaStream (e.g. from the voice provider's speaker/microphone
// events). Sample it with an AnalyserNode into a rolling 0..1 window — the visualizer only ever sees levels,
// so the same mapping serves the bars and line versions alike.
const WINDOW_SIZE = 48;
const TICK_MS = 80;
​
function useStreamLevels(stream: MediaStream | null) {
const [levels, setLevels] = useState<number[]>(() => Array.from({ length: WINDOW_SIZE }, () => 0));
​
useEffect(() => {
if (!stream) return;
const context = new AudioContext();
const analyser = context.createAnalyser();
analyser.fftSize = 1024;
context.createMediaStreamSource(stream).connect(analyser);
const samples = new Uint8Array(analyser.fftSize);
​
const timer = setInterval(() => {
analyser.getByteTimeDomainData(samples);
let sum = 0;
for (const sample of samples) {
const centered = (sample - 128) / 128;
sum += centered * centered;
}
const rms = Math.sqrt(sum / samples.length);
setLevels((previous) => [...previous.slice(1), Math.min(1, rms * 4)]);
}, TICK_MS);
​
return () => {
clearInterval(timer);
void context.close();
};
}, [stream]);
​
return levels;
}
​
export function Example({ voiceStream }: { voiceStream: MediaStream | null }) {
const levels = useStreamLevels(voiceStream);
​
return <AudioVisualizer levels={levels} />;
}
​

Raw code

Primary installed agent source (Bars version)

Component
src/registry/sources/refined/audio-visualizer.tsx
import type { CSSProperties } from "react";
​
import type { AudioVisualizerProps } from "@/components/refined-ui/contracts";
import { cn } from "@/components/refined-ui/lib/cn";
import { skinSlot } from "@/components/refined-ui/skin";
​
// BARS version of the audio-visualizer usage family (the default). audio-visualizer-line.tsx is the sibling
// version: same `AudioVisualizer` export, same AudioVisualizerProps contract (contracts.ts), different rendering —
// installing either gives you <AudioVisualizer />, and swapping versions is an import-path change only.
// Levels-driven and runtime-agnostic: feed it any rolling 0..1 window (AnalyserNode RMS, runner audio stream, …).
​
type AudioVisualizerLevelStyle = CSSProperties & Record<"--audio-visualizer-level", string>;
​
function visibleAudioLevels(levels: readonly number[], points?: number) {
const count = Math.max(1, Math.floor(points ?? levels.length));
return levels.slice(-count).map((level) => Math.min(1, Math.max(0, level)));
}
​
export function AudioVisualizer({ levels, points, className, ...props }: AudioVisualizerProps) {
const visible = visibleAudioLevels(levels, points);
​
return (
<div
data-ui="agent"
data-component="audio-visualizer"
data-variant="bars"
aria-hidden="true"
className={cn(
"flex h-7 w-44 shrink-0 items-center justify-end gap-0.5 overflow-hidden rounded-[var(--radius-control)] px-1",
skinSlot("audio-visualizer", { variant: "bars" }),
className,
)}
{...props}
>
{visible.map((level, index) => {
const levelStyle: AudioVisualizerLevelStyle = { "--audio-visualizer-level": `${level}` };
return (
// biome-ignore lint/suspicious/noArrayIndexKey: bars are a rolling window of levels; their identity is purely positional.
<span key={`${index}-${visible.length}`} className="flex h-full w-1 shrink-0 items-center">
<span
className="block h-[calc(var(--audio-visualizer-level)*100%)] min-h-1 w-full rounded-full bg-primary/70 transition-[height] duration-[var(--duration-fast)] ease-[var(--ease-standard)]"
style={levelStyle}
/>
</span>
);
})}
</div>
);
}
​

On this page

  • Preview
  • Composition
  • Installation
  • Usage
  • Source