A level meter in decibels for one or more channels, with peak hold.
Base UI
"use client";
import { Meter } from "@/components/audio/elements/meter";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.
[left, right]), from the value prop or a live source. The number of readings sets the number of channels.use-audio-levels hook, Tone.js or another source computes the peak and its decay.clipThreshold (default 0 dB) get data-clipping, to style a clip indicator.role="meter" with aria-valuenow, aria-valuemin, aria-valuemax and a value text in dB ("-6 dB"). It is not an aria-live region, so screen readers read the level when asked instead of announcing every update. aria-label names the group; with several channels, each is named "… 1", "… 2".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>import { Meter } from "@/components/audio/meter";Static levels:
<Meter value={[-9, -14]} />With a peak-hold mark:
<Meter peak={[-4, -8]} value={[-9, -14]} />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.
The use-audio-levels hook (npx shadcn@latest add @audio/use-audio-levels) returns such a source. It samples RMS level (in dB) from one AnalyserNode per channel every animation frame, with a peak-hold level that holds the loudest recent level for 1.5s before decaying:
const levels = useAudioLevels([leftAnalyser, rightAnalyser]);
<Meter aria-label="Stereo output" source={levels} />;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;
}To read the levels in React as well, for a numeric readout for example, use useSyncExternalStore(levels.subscribe, levels.getSnapshot).
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.Renders one child per channel.
Props · 1
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-clippingThe lit part of the channel, scaled to the level. Renders a <div> element.
Data attributes · 1
data-clippingThe peak-hold mark, positioned at the channel's peak level; hidden while the channel has none. Renders a <div> element.
Data attributes · 1
data-clippingThe 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.
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.Meter accepts style props (size, peakMarks, marks, className) plus primitive props from MeterPrimitive.Root.Props.
Props · 9
value and peak.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.
Base UI
"use client";
import { Meter } from "@/components/audio/elements/meter";