Skip to content

Meter ​

A level against a scale, read at a glance: how full it is, and where a target or a limit sits on the same scale. Loudness against a target, storage against a quota, a budget against its limit.

tsx
import { Meter } from '@basmilius/desktop-ui';
tsx
import { Meter, useNow } from '@basmilius/desktop-ui';
import { formatDecimal, formatPercent } from '@basmilius/desktop-ui/format';

const FLOOR = -40;
const TOP = -4;
const TARGET = -14;

// A loudness that swells and dips, as a song under the playhead would.
const loudnessAt = (seconds: number, speed: number): number => -20 + 7 * Math.sin(seconds * speed) + 3 * Math.sin(seconds * speed * 3.7);

export default function MeterDemo() {
    const now = useNow(100);
    const seconds = now / 1000;
    const rows = [
        { letter: 'M', name: 'Momentary loudness', loudness: loudnessAt(seconds, 2.3) },
        { letter: 'S', name: 'Short-term loudness', loudness: loudnessAt(seconds, 0.6) }
    ];

    return (
        <div className="flex w-full max-w-xs flex-col gap-6">
            <div role="group" aria-label={`Loudness against ${TARGET} LUFS`} className="flex flex-col gap-0.5">
                {rows.map((row) => (
                    <div key={row.letter} className="flex items-center gap-1.5 text-xs">
                        <span aria-hidden className="w-2.5 text-text-faint">
                            {row.letter}
                        </span>
                        <Meter
                            value={row.loudness}
                            min={FLOOR}
                            max={TOP}
                            marks={[TARGET]}
                            label={row.name}
                            valueText={`${formatDecimal(row.loudness)} LUFS`}
                            className="flex-1"
                        />
                        <span aria-hidden className="w-8 text-right text-text-muted tabular-nums">
                            {formatDecimal(row.loudness)}
                        </span>
                    </div>
                ))}
            </div>
            <div className="flex flex-col gap-1.5 text-xs">
                <div className="flex justify-between text-text-muted">
                    <span>Storage</span>
                    <span className="tabular-nums">{formatPercent(62)}</span>
                </div>
                <Meter value={62} marks={[80]} label="Storage" valueText={`${formatPercent(62)} used`} />
            </div>
        </div>
    );
}

value sits between min and max, 0 and 100 unless you set them, and a level past either end is held to it. null draws the track without a fill, for a level not measured yet. marks draws a line across the track at each value, such as a target or a limit.

tsx
<Meter value={loudness} min={-40} max={-4} marks={[-14]} label="Momentary loudness" valueText={`${formatDecimal(loudness)} LUFS`} />

It is a thin bar of 6 pixels and has no width of its own: className gives it one, or a flex row stretches it. It draws only the bar, so the name and the value beside it are yours to lay out, and two meters stack in a toolbar row. The fill has no transition, so a level that updates many times a second follows without lag.

A screen reader hears it as a meter named by label. valueText is what it hears as the value, such as "-18.2 LUFS" or "Measuring"; without it, it hears the number.

Props ​

PropTypeDefault
valuenumber | nullRequired. null draws an empty track.
labelstringRequired. The name a screen reader reads.
minnumber0
maxnumber100
marksreadonly number[][]Values drawn as lines across the track.
valueTextstringThe value in words for a screen reader.
classNamestringIts width.
refRef<HTMLDivElement>

MeterProps is an exported type.