# UsageMeter

> A quantity used against its limit (count, money or hours) with warning and danger thresholds, an unlimited state, budget burn with a pace projection, and a plan usage panel with an overage estimate strip.

Source: https://docs.nasaqui.com/components/usage-meter

## Install

```bash
npx shadcn@latest add https://docs.nasaqui.com/r/usage-meter.json
```

How much of a limit is used. `UsageMeter` is one row: label, "used of limit", a bar that turns warning at 75% and danger at 90%, and a line saying so ("Approaching the limit", "Over the limit by 2,000"). It is built on the Nasaq `Meter`. `BudgetBurn` shows a project budget in hours and money with a tick for how much of the period has passed and a projected end figure. `UsageSummary` is the plan usage section: all resources, plus an estimated overage strip.

## When to use

- Seats, storage, API calls, AI spend, project hours against a plan or budget.
- A plan usage or billing page that needs to show what the next invoice will add.

## When not to use

- Work that is running (an upload, an import): use [`Progress`](https://docs.nasaqui.com/components/progress).
- A single headline number with a trend: use [`StatCard`](https://docs.nasaqui.com/components/stat-card).
- Editing the limits themselves: use [`LimitsEditor`](https://docs.nasaqui.com/components/limits-editor).

## Import

```tsx
import { BudgetBurn, UsageMeter, UsageSummary } from "@fadymondy/nasaq/web";
```

## Quick start

```tsx
import { UsageMeter } from "@fadymondy/nasaq/web";

export function Seats() {
  return (
    <>
      <UsageMeter label="Seats" used={46} limit={50} unit="seats" />
      <UsageMeter label="Storage" used={12} limit={null} unit="GB" />
      <UsageMeter label="AI spend" kind="money" currency="USD" used={182} limit={200} />
    </>
  );
}
```

## Anatomy

```
UsageMeter          data-slot="usage-meter"  data-tone="ok|warning|danger|over"  data-unlimited
  usage-meter-label / usage-meter-value
  Meter             data-slot="meter"        (or usage-meter-unlimited badge)
  usage-meter-marker  (when marker is set)
  usage-meter-status  (warning, danger, over)
BudgetBurn          data-slot="budget-burn"
UsageSummary        data-slot="usage-summary"
  usage-overage-strip  usage-overage-total
```

## API

### UsageMeter

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `label` | `ReactNode` | required | What is metered. Localise it. |
| `ariaLabel` | `string` | label when a string | Accessible name when `label` is a node. |
| `used` | `number` | required | Amount used. |
| `limit` | `number \| null` | required | The limit; `null` is unlimited (no bar, an Unlimited badge). |
| `kind` | `"count" \| "money" \| "hours"` | `"count"` | How amounts are formatted. |
| `unit` | `string` | none | Noun after a count ("seats", "GB"). |
| `currency` | `string` | `"USD"` (`"SAR"` in Arabic) | ISO 4217 code for `money`. |
| `thresholds` | `{ warnAt?: number; dangerAt?: number }` | 0.75 and 0.9 | Fractions of the limit for warning and danger. |
| `marker` | `number` | none | Fraction of the period passed; draws a tick on the bar. |
| `hint` | `ReactNode` | remaining amount | End-of-row text such as a projection. |
| `size` | `"sm" \| "md"` | `"md"` | Bar thickness. |
| `labels` | `UsageMeterLabels` | en / ar | Override any string. |

### BudgetBurn

`hours?: { used, budget }`, `money?: { used, budget, currency? }`, `elapsed?: number` (0 to 1), `thresholds`, `labels`. With `elapsed`, each bar projects the end of the period at the current rate.

### UsageSummary

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `planName` | `ReactNode` | required | Shown in the badge. |
| `period` | `ReactNode` | "Current period" | Localised period text. |
| `items` | `UsageItem[]` | required | `{ id, label, used, limit, kind?, unit?, overageRate?, hint? }`. |
| `currency` | `string` | `"USD"` (`"SAR"` in Arabic) | For the estimate and money items. |
| `onUpgrade` | `() => void` | none | Shows Upgrade plan when any item is near or over its limit. |
| `loading` | `boolean` | `false` | Skeleton layout. |

The overage estimate is the sum over items of `max(0, used - limit) * overageRate`. The pure helpers `usageTone`, `usageFraction`, `overageAmount`, `overageTotal` and `burnProjection` are exported too.

## Examples

Budget burn:

```tsx
import { BudgetBurn } from "@fadymondy/nasaq/web";

export const Burn = () => <BudgetBurn hours={{ used: 96, budget: 160 }} money={{ used: 7200, budget: 12000, currency: "USD" }} elapsed={0.5} />;
```

Plan page:

```tsx
import { UsageSummary } from "@fadymondy/nasaq/web";

export const Page = () => (
  <UsageSummary
    planName="Team"
    currency="USD"
    items={[
      { id: "seats", label: "Seats", used: 46, limit: 50, unit: "seats" },
      { id: "calls", label: "API calls", used: 1_200_000, limit: 1_000_000, overageRate: 0.00001 },
    ]}
  />
);
```

Arabic: wrap the app in `NasaqProvider locale="ar"`; the strings switch and the bar fills from the right.

## Accessibility

The bar is a Base UI `meter` with an accessible name and a spoken value ("46 of 50"). The state is written out with an icon and a sentence, so it never depends on colour. Over the limit the sentence has `role="alert"`. The projection and the overage strip are plain text.

## RTL & i18n

- Layout uses logical properties; the fill and the period tick start at the inline start.
- Amounts are formatted with the active locale and isolated with `<bdi>` so "1,200 seats" keeps its order in Arabic.
- Every string has an English and Arabic default; override with `labels`.

## Styling & tokens

- Fill colours come from `--primary`, `--nq-warning`, `--nq-danger` through `Meter`.
- Target `[data-slot="usage-meter"][data-tone="danger"]` for state styling.

## Do / Don't

- Do give an `overageRate` only when the plan really charges past the limit.
- Do pass `elapsed` to a budget so a fast burn is visible before the limit is hit.
- Don't hide unlimited resources; the Unlimited badge tells the reader nothing will run out.
- Don't rely on the bar colour alone to say a limit is close.

## Related

- [`Progress`](https://docs.nasaqui.com/components/progress)
- [`StatCard`](https://docs.nasaqui.com/components/stat-card)
- [`PlanCard`](https://docs.nasaqui.com/components/plan-card)

## Lab

https://docs.nasaqui.com/?path=/docs/components-data-display-usage-meter--docs

## Code

### React

```tsx
import { UsageMeter } from "@fadymondy/nasaq/web";

export function Seats() {
  return (
    <>
      <UsageMeter label="Seats" used={46} limit={50} unit="seats" />
      <UsageMeter label="Storage" used={12} limit={null} unit="GB" />
      <UsageMeter label="AI spend" kind="money" currency="USD" used={182} limit={200} />
    </>
  );
}
```

### shadcn

```tsx
import { UsageMeter } from "@/components/ui/usage-meter";

export function Seats() {
  return (
    <>
      <UsageMeter label="Seats" used={46} limit={50} unit="seats" />
      <UsageMeter label="Storage" used={12} limit={null} unit="GB" />
      <UsageMeter label="AI spend" kind="money" currency="USD" used={182} limit={200} />
    </>
  );
}
```

### Vue

```vue
<script setup lang="ts">
import { NqUsageMeter } from "@fadymondy/nasaq/vue";
</script>

<template>
  <div class="flex flex-col gap-4">
    <NqUsageMeter label="Seats" :used="46" :limit="50" unit="seats" />
    <NqUsageMeter label="Storage" :used="12" :limit="null" unit="GB" />
    <NqUsageMeter label="AI spend" kind="money" currency="USD" :used="182" :limit="200" />
  </div>
</template>
```

### Blade

```blade
<div class="flex flex-col gap-4">
    <x-nq::usage-meter label="Seats" :used="46" :limit="50" unit="seats" />
    <x-nq::usage-meter label="Storage" :used="12" :limit="null" unit="GB" />
    <x-nq::usage-meter label="AI spend" kind="money" currency="USD" :used="182" :limit="200" />
</div>
```

### HTML + Alpine

```html
<div class="flex flex-col gap-4">
    <div data-slot="usage-meter" data-tone="danger"  class="flex min-w-0 flex-col gap-1.5">
    <div class="flex items-baseline justify-between gap-3 text-body-sm">
        <span data-slot="usage-meter-label" class="min-w-0 truncate text-label text-foreground">Seats</span>
        <span data-slot="usage-meter-value" class="shrink-0 text-muted-foreground tabular-nums"><bdi class="text-foreground">46 seats</bdi> of <bdi>50 seats</bdi></span>
    </div>
            <div class="relative">
            <div data-slot="meter" data-tone="danger" role="meter"     aria-valuemin="0" aria-valuemax="50" aria-valuenow="46" aria-valuetext="46%"
    aria-label="Seats" class="flex w-full flex-col gap-1.5">
        <div data-slot="meter-track" class="relative block w-full overflow-hidden rounded-full bg-nq-surface-soft h-2">
        <div data-slot="meter-indicator" style="inset-inline-start:0;width:92%" class="block h-full rounded-full transition-[width] duration-300 ease-nq motion-reduce:transition-none bg-nq-danger"></div>
    </div>
</div>
                    </div>
                    <div class="flex flex-wrap items-center justify-between gap-x-3 text-caption text-muted-foreground">
                                    <span data-slot="usage-meter-status"  class="inline-flex items-center gap-1 text-label text-nq-danger-text">
                        <svg aria-hidden="true" class="size-3.5 shrink-0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <circle cx="12" cy="12" r="10"/>
  <line x1="12" x2="12" y1="8" y2="12"/>
  <line x1="12" x2="12.01" y1="16" y2="16"/>
</svg>                        Almost at the limit
                    </span>
                                                    <span>4 seats left</span>
                            </div>
            </div>
    <div data-slot="usage-meter" data-tone="ok"  data-unlimited="true"  class="flex min-w-0 flex-col gap-1.5">
    <div class="flex items-baseline justify-between gap-3 text-body-sm">
        <span data-slot="usage-meter-label" class="min-w-0 truncate text-label text-foreground">Storage</span>
        <span data-slot="usage-meter-value" class="shrink-0 text-muted-foreground tabular-nums"><bdi class="text-foreground">12 GB</bdi></span>
    </div>
            <div>
            <span data-slot="usage-meter-unlimited" class="inline-flex h-5 shrink-0 items-center gap-1 whitespace-nowrap rounded-[4px] border border-border bg-secondary px-1.5 text-caption font-medium text-foreground [&_svg]:size-3"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M6 16c5 0 7-8 12-8a4 4 0 0 1 0 8c-5 0-7-8-12-8a4 4 0 1 0 0 8"/>
</svg>Unlimited</span>
        </div>
    </div>
    <div data-slot="usage-meter" data-tone="danger"  class="flex min-w-0 flex-col gap-1.5">
    <div class="flex items-baseline justify-between gap-3 text-body-sm">
        <span data-slot="usage-meter-label" class="min-w-0 truncate text-label text-foreground">AI spend</span>
        <span data-slot="usage-meter-value" class="shrink-0 text-muted-foreground tabular-nums"><bdi class="text-foreground">$182</bdi> of <bdi>$200</bdi></span>
    </div>
            <div class="relative">
            <div data-slot="meter" data-tone="danger" role="meter"     aria-valuemin="0" aria-valuemax="200" aria-valuenow="182" aria-valuetext="182%"
    aria-label="AI spend" class="flex w-full flex-col gap-1.5">
        <div data-slot="meter-track" class="relative block w-full overflow-hidden rounded-full bg-nq-surface-soft h-2">
        <div data-slot="meter-indicator" style="inset-inline-start:0;width:91%" class="block h-full rounded-full transition-[width] duration-300 ease-nq motion-reduce:transition-none bg-nq-danger"></div>
    </div>
</div>
                    </div>
                    <div class="flex flex-wrap items-center justify-between gap-x-3 text-caption text-muted-foreground">
                                    <span data-slot="usage-meter-status"  class="inline-flex items-center gap-1 text-label text-nq-danger-text">
                        <svg aria-hidden="true" class="size-3.5 shrink-0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <circle cx="12" cy="12" r="10"/>
  <line x1="12" x2="12" y1="8" y2="12"/>
  <line x1="12" x2="12.01" y1="16" y2="16"/>
</svg>                        Almost at the limit
                    </span>
                                                    <span>$18 left</span>
                            </div>
            </div>
</div>
```
