# Usage Meter

> Stacked usage meter with a legend and warnings when nearing or exceeding the limit.

- Author: Atelier UI
- License: MIT
- Page: https://chromotion.dev/en/components/usage-meter
- Site: Atelier UI (https://chromotion.dev)

## Install

```bash
npx shadcn@latest add https://chromotion.dev/r/usage-meter.json
```

Stack: React + TypeScript + Tailwind CSS v4, shadcn/ui conventions. `cn` comes from `@/lib/utils`.

## Dependencies

No extra dependencies.

## Usage

```tsx title="components/usage-meter-demo.tsx"
"use client"

import { UsageMeter } from "@/components/usage-meter"

export default function UsageMeterDemo() {
  return (
    <div className="grid w-full max-w-md gap-5">
      <UsageMeter
        title="Storage"
        unit="GB"
        limit={100}
        segments={[
          { label: "Documents", value: 32, color: "#6366f1" },
          { label: "Media", value: 41, color: "#06b6d4" },
          { label: "Backups", value: 14, color: "#f59e0b" },
        ]}
      />
      <UsageMeter
        title="API requests"
        unit="k"
        limit={500}
        segments={[
          { label: "Production", value: 380, color: "#10b981" },
          { label: "Staging", value: 142, color: "#8b5cf6" },
        ]}
      />
    </div>
  )
}
```

## Source

```tsx title="components/usage-meter.tsx"
import * as React from "react"

import { cn } from "@/lib/utils"

export interface UsageSegment {
  label: string
  value: number
  /** Any CSS color */
  color: string
}

export interface UsageMeterProps extends Omit<React.ComponentProps<"div">, "title"> {
  title: string
  segments: UsageSegment[]
  limit: number
  unit?: string
  /** Fraction of the limit at which the meter turns to a warning */
  warnAt?: number
}

const format = (n: number) => new Intl.NumberFormat("en-US", { maximumFractionDigits: 1 }).format(n)

export function UsageMeter({ title, segments, limit, unit = "", warnAt = 0.85, className, ...props }: UsageMeterProps) {
  const used = segments.reduce((sum, s) => sum + s.value, 0)
  const ratio = limit === 0 ? 0 : used / limit
  const over = used > limit
  const warn = ratio >= warnAt

  return (
    <div {...props} className={cn("w-full max-w-md rounded-2xl border bg-card p-5", className)}>
      <div className="flex items-baseline justify-between gap-3">
        <h3 className="text-sm font-semibold">{title}</h3>
        <p className="text-sm tabular-nums text-muted-foreground">
          <span className={cn("font-semibold text-foreground", over && "text-destructive")}>{format(used)}</span> / {format(limit)} {unit}
        </p>
      </div>

      <div
        role="meter"
        aria-label={title}
        aria-valuemin={0}
        aria-valuemax={limit}
        aria-valuenow={Math.min(used, limit)}
        aria-valuetext={`${format(used)} of ${format(limit)} ${unit}`}
        className="mt-3 flex h-3 overflow-hidden rounded-full bg-muted"
      >
        {segments.map((segment) => (
          <div
            key={segment.label}
            title={`${segment.label}: ${format(segment.value)} ${unit}`}
            className="h-full origin-left transition-[width] duration-700 ease-out first:rounded-l-full last:rounded-r-full [&:not(:first-child)]:border-l-2 [&:not(:first-child)]:border-card"
            style={{ width: `${Math.min(100, (segment.value / Math.max(limit, used)) * 100)}%`, backgroundColor: segment.color }}
          />
        ))}
      </div>

      <ul className="mt-4 grid gap-1.5 text-sm">
        {segments.map((segment) => (
          <li key={segment.label} className="flex items-center gap-2">
            <span aria-hidden className="size-2.5 rounded-full" style={{ backgroundColor: segment.color }} />
            <span className="text-muted-foreground">{segment.label}</span>
            <span className="ml-auto tabular-nums">{format(segment.value)}</span>
          </li>
        ))}
      </ul>

      {warn && (
        <p
          role="status"
          className={cn(
            "mt-4 rounded-lg px-3 py-2 text-sm",
            over ? "bg-destructive/10 text-destructive" : "bg-amber-500/10 text-amber-700 dark:text-amber-300",
          )}
        >
          {over ? `You are ${format(used - limit)} ${unit} over your limit.` : `You have used ${Math.round(ratio * 100)}% of your plan.`}
        </p>
      )}
    </div>
  )
}
```
