Skip to content
EMBOSS
Docs menu

Meter

A level meter milled into the chassis — zone colors are painted into the channel and a shutter covers what the value hasn't reached.

@emboss/meter
Master
x

Installation

pnpm dlx shadcn@latest add @emboss/meter

Usage

example.tsx
import { Meter } from "@/components/ui/meter";

export function Example() {
  return <Meter value={64} aria-label="Master level" />;
}

API reference

Meter

Read-only meter semantics from the primitive; label with aria-label or the MeterLabel part.

PropTypeDefaultDescription
valuenumberCurrent reading.
min / maxnumber / number0 / 100Range of the scale.
warnAt / dangerAtnumber / number0.7 / 0.9Zone boundaries as fractions of the range.

MeterLabel / MeterValue

Optional labelled readout parts, wired to the meter by the primitive.

Source

View source — meter.tsx
meter.tsx
"use client";

import { Meter as BaseMeter } from "@base-ui/react/meter";
import { cn } from "@/lib/utils";

export type MeterProps = React.ComponentProps<typeof BaseMeter.Root> & {
  /** Where the warning zone begins, as a fraction of the range. */
  warnAt?: number;
  /** Where the danger zone begins, as a fraction of the range. */
  dangerAt?: number;
};

/**
 * A level meter milled into the chassis. The full zone strip — safe, warning,
 * danger — is painted into the channel and a shutter covers whatever the
 * current value hasn't reached, so the reading is position plus color.
 *
 * @example
 * <Meter value={-6.2} min={-60} max={0} aria-label="Master level" />
 */
function Meter({
  className,
  children,
  warnAt = 0.7,
  dangerAt = 0.9,
  value,
  min = 0,
  max = 100,
  ...props
}: MeterProps) {
  const fraction = Math.min(1, Math.max(0, (value - min) / (max - min || 1)));
  return (
    <BaseMeter.Root
      data-slot="meter"
      value={value}
      min={min}
      max={max}
      {...props}
    >
      <BaseMeter.Track
        data-slot="meter-track"
        className={cn(
          "relative block h-3 w-full overflow-hidden rounded-sm shadow-deboss-2",
          className,
        )}
        style={{
          background: `linear-gradient(to right, var(--positive) 0 ${warnAt * 100}%, var(--warning) ${warnAt * 100}% ${dangerAt * 100}%, var(--danger) ${dangerAt * 100}% 100%)`,
        }}
      >
        <BaseMeter.Indicator
          data-slot="meter-indicator"
          className="block h-full"
        />
        {/* The shutter: covers the span the value has not reached. */}
        <span
          aria-hidden
          className="absolute inset-y-0 right-0 bg-well-deep transition-[left] duration-(--duration-settle) ease-(--ease-settle)"
          style={{ left: `${fraction * 100}%` }}
        />
      </BaseMeter.Track>
      {children}
    </BaseMeter.Root>
  );
}

function MeterLabel({
  className,
  ...props
}: React.ComponentProps<typeof BaseMeter.Label>) {
  return (
    <BaseMeter.Label
      data-slot="meter-label"
      className={cn("text-sm font-medium text-ink", className)}
      {...props}
    />
  );
}

function MeterValue({
  className,
  ...props
}: React.ComponentProps<typeof BaseMeter.Value>) {
  return (
    <BaseMeter.Value
      data-slot="meter-value"
      className={cn("font-mono text-sm text-ink-muted tabular-nums", className)}
      {...props}
    />
  );
}

export { Meter, MeterLabel, MeterValue };