Arrangement
Transport and mixer
EQ and filters
Modulation
Utility
Instruments
Audio UI
Shows audio levels for a stereo output, a channel strip or any multi-channel source. The registry component wraps the Meter primitive from @audio-ui/react: one bar per channel, a destructive color when a channel clips, and an optional peak-hold mark. The meter only displays levels: it takes no pointer or keyboard input, and no focus.
Overview
- Value: one dB reading per channel (stereo is
[left, right]), from thevalueprop or a livesource. The number of readings sets the number of channels. - Peak hold: an optional peak level per channel places a peak-hold mark. The primitive has no clock: your source, Tone.js or another library computes the peak and its decay.
- Clipping: channels at or above
clipThreshold(default0dB) getdata-clipping, to style a clip indicator. - Accessibility: each channel is a
role="meter"witharia-valuenow,aria-valuemin,aria-valuemaxand a value text in dB ("-6 dB"). It is not anaria-liveregion, so screen readers read the level when asked instead of announcing every update.aria-labelnames the group; with several channels, each is named "… 1", "… 2".
Installation
pnpm dlx shadcn@latest add @audio-ui/meter
Anatomy
Import the primitive and assemble its parts.
import { Meter } from "@audio-ui/react";
<Meter.Root>
<Meter.Channels>
{(index) => (
<Meter.Channel index={index}>
<Meter.Indicator />
<Meter.Peak />
</Meter.Channel>
)}
</Meter.Channels>
</Meter.Root>Usage
import { Meter } from "@/components/audio-ui/meter";Static levels:
<Meter value={[-9, -14]} />With a peak-hold mark:
<Meter peak={[-4, -8]} value={[-9, -14]} />Feeding real levels
value and peak are plain props: fine for static or slowly changing levels, but a signal sampled 60 times a second would re-render React on every frame. For live audio, pass a source instead: the meter subscribes to it and writes each frame to the DOM, without a React render.
A source is any object with the shape of an external store, so you can bring your own (Tone.js, a worklet, a WebSocket):
interface MeterSource {
getSnapshot: () => { levels: readonly number[]; peaks?: readonly number[] };
subscribe: (listener: () => void) => () => void;
}createSource from @audio-ui/react makes one you push to. The use-audio-levels hook is one: it samples the RMS level of one AnalyserNode per channel every animation frame, lets it fall back smoothly, and holds each channel's peak a moment before it falls.
pnpm dlx shadcn@latest add @audio-ui/use-audio-levels
import { Meter } from "@/components/audio-ui/meter";
import { useAudioLevels } from "@/hooks/use-audio-levels";
// One analyser per channel, made once: a new array restarts the sampling.
const analysers = useMemo(() => {
const analyser = ctx.createAnalyser();
output.connect(analyser); // a tap: the analyser plays nothing
return [analyser];
}, [ctx, output]);
const levels = useAudioLevels(analysers);
<Meter aria-label="Output" source={levels} />;For a stereo meter, split the signal with a ChannelSplitter and give the hook one analyser per side. The hook and the other analysis tools are in Analysis.
To read the levels in React as well, for a numeric readout for example, use useSyncExternalStore(levels.subscribe, levels.getSnapshot).
API reference
Root
Groups the channels of a level meter. Levels come either from value (and peak) as React props, or from a live source: with a source the meter writes each frame straight to the DOM, so a signal updating 60 times a second never re-renders React. Renders a <div> element.
Props · 11
Meter.Value and the channels' aria-valuetext. Defaults to one decimal."iec"), or by your own increasing curve.data-warning. Unset, there is no warning zone.value and peak.Channels
Renders one child per channel.
Props · 1
Channel
One channel of the meter, a role="meter": name it with aria-label or a Meter.Label. Carries data-clipping at or above the clip threshold. Renders a <div> element.
Props · 2
aria-valuetext, e.g. "-6 dB". Defaults to the formatted level.Data attributes · 1
data-clippingIndicator
The lit part of the channel, scaled to the level. Renders a <div> element.
Data attributes · 1
data-clippingPeak
The peak-hold mark, positioned at the channel's peak level; hidden while the channel has none. Renders a <div> element.
Data attributes · 1
data-clippingValue
The channel's formatted level (format/locale on Root). With a live source only the default text is kept current, not a children function. Renders a <span> element.
Mark
A graduation of the level scale at value, such as -18 dB: placed along the meter's length from its minimum, upward for a vertical meter and rightward for a horizontal one. Sits directly in Root, beside the channels. Decorative: each channel announces its own level. Renders a <div> element.
Props · 1
min and max.Registry component
Meter accepts style props (size, peakMarks, marks, className) plus primitive props from MeterPrimitive.Root.Props.
Value and state
Props · 9
value and peak.Layout and display
Props · 5
[6, 0, -6, -12, -20, -30, -40, -60].@audio-ui/react/meter exposes Meter.Root (the group), Meter.Channels (one child per channel), Meter.Channel (a role="meter"), Meter.Track, Meter.Indicator (the lit part), Meter.Peak (the peak-hold mark), Meter.Value (the formatted level) and Meter.Label. Channel parts expose data-orientation and data-clipping.
Examples
Mixing console
Each channel's meter reads its real signal after the fader, and the master's reads both sides.
Audio UI
Horizontal orientation
Audio UI
More Shadcn Meter components
Browse 3 production-ready audio/ui Meter components for players, mixers, and synth interfaces. These examples use the headless primitives from @audio-ui/react and stay fully compatible with Shadcn Create so radius, color, and typography match your configured theme.
Browse all 3 Shadcn Meter components for copy-ready layouts, dashboards, and forms built with Tailwind CSS in the audio/ui library.