WebberUI

Audio Visualizer

Web Audio API audio spectrum visualization drawn in canvas 2D, in bar, mirror, and radial styles, able to analyze a real audio source or drive a decorative animation from synthetic data.

This is a WebberUI Pro component

Free during the launch campaign: sign up or sign in, then hit “Copy install command” in the preview above and it installs straight away — no payment, no credit card. The command below returns 401 while you are signed out.

How to install Pro components →See the plans →

Loading preview…
npx shadcn@latest add "https://webberui.com/r/audio-visualizer.json?t=<install token>"

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

48
1
<AudioVisualizer />

Installation

npx shadcn@latest add "https://webberui.com/r/audio-visualizer.json?t=<install token>"

Or, once registries are configured in components.json, install it as @webberui/audio-visualizer.

Usage

With no audio source provided, the component is driven by procedurally synthesized data, which makes it a good fit for a decorative animation:

import { AudioVisualizer } from "@/components/ui/audio-visualizer";

<div className="h-40 overflow-hidden rounded-xl">
  <AudioVisualizer variant="bars" />
</div>

Analyzing a real media element

Pass the ref of an <audio> / <video> element to media and the component reads the live spectrum through the Web Audio API. The media's sound is played back through the audio graph, so it stays audible:

"use client";

import * as React from "react";
import { AudioVisualizer } from "@/components/ui/audio-visualizer";

export function Player() {
  const audioRef = React.useRef<HTMLAudioElement>(null);
  const [playing, setPlaying] = React.useState(false);

  return (
    <div>
      <audio ref={audioRef} src="/track.mp3" />
      <AudioVisualizer
        variant="mirror"
        media={audioRef}
        active={playing}
        color="#22d3ee"
        className="h-40"
      />
      <button
        onClick={() => {
          const el = audioRef.current;
          if (!el) return;
          if (playing) el.pause();
          else void el.play();
          setPlaying(!playing);
        }}
      >
        Play / Pause
      </button>
    </div>
  );
}

Analyzing the microphone

Pass the MediaStream you get from getUserMedia to stream (the microphone is not played back, which avoids feedback):

const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
<AudioVisualizer variant="radial" stream={stream} sensitivity={1.4} />

Props

PropTypeDefaultDescription
variant"bars" | "mirror" | "radial""bars"Visualization style: bars from the bottom, mirrored about a center line, or radial around a circle
mediaRefObject<HTMLMediaElement | null>Ref of the <audio>/<video> to analyze; once provided, the real spectrum is analyzed
streamMediaStream | nullnullSupply an audio stream directly (the microphone, for example); takes precedence over media
activebooleantrueWhether the animation runs; at false it freezes into a still low-amplitude state
barsnumber48Number of frequency bands (rays for radial), clamped internally to 4–256
colorstringa theme-following neutralPrimary color; when not specified it follows currentColor (which switches with the light/dark theme)
fftSizenumber2048FFT window size; must be a power of two (32–32768)
smoothingnumber0.8Spectrum smoothing coefficient 0–1; the higher, the smoother and the slower to react
sensitivitynumber1Sensitivity multiplier, amplifying the overall amplitude
labelstring"音訊視覺化" (Audio visualization)Accessible label applied as the container's aria-label
classNamestringClass appended to the outermost container

How it works

  • Two data-source modes. When media or stream is provided, an AnalyserNode is created and the real spectrum is read each frame with getByteFrequencyData, then grouped into bars bands on an "approximately logarithmic" scale that emphasizes the low end; with no audio source it falls back to procedurally synthesized data (multi-layer sine oscillation plus beat pulses), so a demo environment can show it off without any external audio file
  • A single canvas 2D layer. All three styles are drawn on the same <canvas>: bars rise from the bottom, mirror mirrors above and below a center line, and radial radiates outward around a circle; each frame is redrawn after a clearRect, with rounded corners from the native roundRect (falling back to hand-drawn arcTo where it is unsupported)
  • MediaElementSource can only be created once. A given media element can only have a source node created for it once in its whole lifetime, and creating one again throws; the component caches the "context / source / analyser" audio graph in a WeakMap and reuses it across remounts and re-renders, releasing it automatically once the element is collected
  • AudioContext lifecycle. A context created from a microphone stream is close()d on unmount; the audio graph for a media element is not closed, because it is cached for reuse. If the context is suspended (the browser requires a user gesture), resume() is attempted while active
  • The real canvas size is set from devicePixelRatio (capped at 2) and then scaled with setTransform, so Retina screens stay sharp; a ResizeObserver watches for container changes and redraws immediately
  • When color is not specified, the canvas's currentColor is read (resolved from a text-neutral-* class), and a MutationObserver watches class changes on <html> so the color is re-read the moment the light/dark theme switches

Accessibility

  • The container carries role="img" and a customizable aria-label (「音訊視覺化」, Audio visualization, by default), while the inner <canvas> is marked aria-hidden so the per-frame image is not announced
  • When the user has "reduce motion" enabled at the system level, the requestAnimationFrame loop is disabled entirely and only a single still low-amplitude silhouette is rendered, with the DOM structure unchanged (no two-stage mount needed)
  • The visualization is a decorative mapping of sound the user is already hearing; make sure the actual playback controls (the play/pause button and so on) have complete keyboard and labelling support of their own

On this page