Level Meter
Segmented audio level meter with peak-hold. Zone-tinted segments light in proportion to a live value, with a decaying peak line — the live vertical sibling of Meter.
<arc-level-meter> Overview
>
LevelMeter is built for values that move at signal rate — audio levels first, but equally buffer fill, network throughput, or any telemetry that updates many times a second. It renders a track of discrete segments (twenty by default) that light up in proportion to `value`, each tinted by the zone it occupies: success below the `warn` threshold, warning between `warn` and `clip`, error above `clip`. Lit segments carry the house glow; unlit ones show a faint preview of their zone so the scale reads even at silence. Set `segments="0"` for a continuous, unsegmented bar.
The value maps linearly between `min` and `max`, which default to 0 and 1. For dB metering set the range directly — `min="-60" max="0"` — and feed dB readings as `value`. The `warn` and `clip` thresholds are always fractions of the range (0.75 and 0.9 by default), so the same zone geometry works for linear and dB scales alike.
A thin peak-hold line rides above the signal. Left alone, the component tracks its own peak from incoming values, holds it for a beat, then decays it toward the current level on an animation frame loop — and under `prefers-reduced-motion` the decay becomes a jump rather than a slide. If your audio engine already computes peaks, set the `peak` property and the line renders exactly there with no tracking of its own.
One element is one channel, deliberately: there is no stereo mode. Compose two meters side by side for a stereo pair — channel count stays the consumer's decision, and layouts from mono to 7.1 fall out of ordinary flex composition rather than a prop fork. The component renders no visible text, so the `label` attribute is the accessible name; `role="meter"` with `aria-valuemin`, `aria-valuemax`, and `aria-valuenow` carries the reading itself.Guidelines
When to use
- Use LevelMeter for live, fast-moving readings — audio channels, input gain, buffer health — where Meter would flicker meaninglessly
- Set `min="-60" max="0"` (or your headroom of choice) and feed dB values directly for audio work
- Compose one meter per channel: two side by side for stereo, a row of them for a mixer
- Give every meter a `label` naming its channel ("Master left", not "Level") — it is the only accessible name the component gets
- Set the `peak` property from your audio engine when it already computes peak values; the built-in tracker is for when it does not
- Tune `warn` and `clip` to your actual headroom — the defaults (0.75/0.9) suit a linear 0..1 signal
When not to use
- Do not use LevelMeter for a static scalar like disk usage or a score — that is Meter, whose low/high/optimum semantics exist for exactly that
- Do not use it for task completion — Progress owns determinate and indeterminate work tracking
- Do not look for a stereo prop; two elements are the stereo pair
- Do not drive `value` from slow polling and expect the peak line to mean much — the tracker is only as live as the data feeding it
- Do not rely on segment color alone to signal clipping to users — pair the meter with a clip indicator or text where it matters
Features
- Segmented display with proportional lighting — segment count configurable, `segments="0"` for a continuous bar
- Three-zone tinting from `warn` and `clip` fractions: success, warning, and error via the `--color-*` tokens, glow on lit segments
- Peak-hold line that tracks incoming values, holds, then decays — or renders a consumer-supplied `peak` exactly
- Linear or dB scales through plain `min`/`max` — thresholds stay fractions of the range either way
- Vertical (default, bottom-up) and horizontal orientations using logical properties, so horizontal meters follow text direction
- Peak decay runs on `requestAnimationFrame`, starts on connect, and stops on disconnect — no work while unmounted
- Honours `prefers-reduced-motion`: the peak line jumps instead of animating its fall
- Semantic `role="meter"` with `aria-valuemin`, `aria-valuemax`, `aria-valuenow`, and a `label`-driven accessible name
- One meter per channel by design — stereo and surround are composition, not configuration
- Exposed CSS parts: meter, track, segment, fill, peak
Preview
Usage
<!-- Stereo pair: one meter per channel -->
<div style="display: flex; gap: 4px;">
<arc-level-meter id="ch-l" label="Master left" min="-60" max="0" value="-60"></arc-level-meter>
<arc-level-meter id="ch-r" label="Master right" min="-60" max="0" value="-60"></arc-level-meter>
</div>
<script>
// Drive it at signal rate; the meter tracks its own decaying peak.
analyzer.onLevels = ({ left, right }) => {
document.getElementById('ch-l').value = left; // dB
document.getElementById('ch-r').value = right;
};
</script> import { LevelMeter } from '@arclux/arc-ui-react';
export default function MasterMeters({ left, right }: { left: number; right: number }) {
return (
<div style={{ display: 'flex', gap: 4 }}>
<LevelMeter label="Master left" min={-60} max={0} value={left} />
<LevelMeter label="Master right" min={-60} max={0} value={right} />
</div>
);
} <script setup>
import { LevelMeter } from '@arclux/arc-ui-vue';
defineProps({ left: Number, right: Number });
</script>
<template>
<div style="display: flex; gap: 4px;">
<LevelMeter label="Master left" :min="-60" :max="0" :value="left" />
<LevelMeter label="Master right" :min="-60" :max="0" :value="right" />
</div>
</template> <script>
import { LevelMeter } from '@arclux/arc-ui-svelte';
let { left, right } = $props();
</script>
<div style="display: flex; gap: 4px;">
<LevelMeter label="Master left" min={-60} max={0} value={left} />
<LevelMeter label="Master right" min={-60} max={0} value={right} />
</div> import { Component, Input } from '@angular/core';
import { LevelMeter } from '@arclux/arc-ui-angular';
@Component({
imports: [LevelMeter],
template: `
<div style="display: flex; gap: 4px;">
<arc-level-meter label="Master left" [min]="-60" [max]="0" [value]="left"></arc-level-meter>
<arc-level-meter label="Master right" [min]="-60" [max]="0" [value]="right"></arc-level-meter>
</div>
`,
})
export class MasterMeters {
@Input() left = -60;
@Input() right = -60;
} import { LevelMeter } from '@arclux/arc-ui-solid';
export default function MasterMeters(props: { left: number; right: number }) {
return (
<div style={{ display: 'flex', gap: '4px' }}>
<LevelMeter label="Master left" min={-60} max={0} value={props.left} />
<LevelMeter label="Master right" min={-60} max={0} value={props.right} />
</div>
);
} import { LevelMeter } from '@arclux/arc-ui-preact';
export default function MasterMeters({ left, right }: { left: number; right: number }) {
return (
<div style={{ display: 'flex', gap: 4 }}>
<LevelMeter label="Master left" min={-60} max={0} value={left} />
<LevelMeter label="Master right" min={-60} max={0} value={right} />
</div>
);
} API
-
valuenumber0 - Current level. Interpreted against `min` and `max`, so with the defaults (0 and 1) it is a linear fraction, and with `min="-60" max="0"` it is a dB reading. Values outside the range are clamped.
-
minnumber0 - Value at the empty end of the meter. Defaults to 0.
-
maxnumber1 - Value at the full end of the meter. Defaults to 1. Use -60..0 (or your headroom of choice) for dB scales.
-
peaknumberundefined - Externally supplied peak-hold level, in the same units as `value`. When set, the component renders the hold line exactly there and does no tracking of its own. When absent, the meter tracks its own peak from incoming values, holds it briefly, then decays it toward the current level.
-
orientation'vertical' | 'horizontal''vertical' - Meter direction. Vertical (the default) fills bottom-up like a channel strip; horizontal fills from the inline start. Unknown values fall back to vertical.
-
segmentsnumber20 - Number of discrete segments. Defaults to 20. Set 0 for a continuous, unsegmented bar.
-
warnnumber0.75 - Fraction of the range (0..1) where the warning zone begins, regardless of units. Defaults to 0.75.
-
clipnumber0.9 - Fraction of the range (0..1) where the clip (error) zone begins. Defaults to 0.9.
-
labelstring'' - Accessible name applied as aria-label on the meter. The component renders no visible text, so this is the only name screen readers get — use something like "Master left" rather than "Level".
See Also
- Meter Semantic gauge display with color-coded fill zones (success, warning, error) based on configurable low/high/optimum thresholds.
- Progress Progress indicator as a bar or spinner, with determinate and indeterminate modes. Shows completion state for uploads, installations, and long-running operations.
- Sparkline Tiny inline SVG chart for embedding lightweight line or bar visualizations inside tables, stat cards, and dashboards. Renders from a simple comma-separated data string with no external charting dependencies.