affectUI › Components › Data display

Usage Meter

Plan usage and AI credits – a stacked credits meter by category, daily sparkline, limits and a button that speaks up near the limit.

Part of affectUI Pro. Install: npx shadcn@latest add https://www.affectui.com/r/usage-meter.json · Import from @/components/ui/usage-meter

When to use

  • The usage or billing page of a SaaS or AI product – credits, tokens, minutes or messages per billing cycle
  • Showing what the usage went into (chat, search, images, API) next to plan limits like seats and storage
  • Nudging people to top up or upgrade right where they see they're running out

When not to use

  • Long-term trends over many months – a line chart like the KPI Chart reads better
  • More than about five categories – the bar gets too thin; group the small ones into "Other"

Usage

import { useState } from "react";
import { UsageMeter } from "@/components/ui/usage-meter";

export function Usage({ usage }) {
  const [period, setPeriod] = useState("month");
  const data = usage[period]; // { used, limits, daily } per period from your API

  return (
    <UsageMeter
      title="Acme AI"
      plan="Pro plan"
      total={10000}
      used={data.used} // [{ label: "Chat", value: 3980 }, { label: "Search", value: 1640 }, …]
      limits={[
        { label: "Seats", used: 8, total: 10 },
        { label: "Storage", used: 3.2, total: 5, unit: "GB" },
        { label: "Projects", used: 12, total: null }, // unlimited
      ]}
      daily={data.daily} // [{ label: "Oct 1", value: 312 }, …, { label: "Oct 17", value: null }]
      periods={[
        { id: "month", label: "This month" },
        { id: "last", label: "Last month" },
        { id: "7d", label: "7 days" },
      ]}
      period={period}
      onPeriodChange={setPeriod}
      resetAt="2026-10-17"
      onUpgrade={(state) => (state === "ok" ? openPlans() : openTopUp())}
    />
  );
}

Props

NameTypeDefaultDescription
totalnumberCredits included in the plan – the bar's full width
used{ label: string; value: number; color?: string }[]What the credits went into in the shown period. Without colors the parts get shades of the accent, then grays; each label keeps its shade
limits{ label; used; total?: number | null; unit?; decimals? }[]Thin meters below, e.g. seats and storage. Leave total out (or null) for unlimited – shown as "of ∞" on a striped track
periods{ id: string; label: string }[]Options of the period switch; no switch when left out
periodstringControlled period id – pass the matching used, limits and daily with it
defaultPeriodstringStarting period when uncontrolled; defaults to the first option
onPeriodChange(id: string) => voidCalled when a period is picked
resetAtDate | string | numberWhen the credits reset – shown as "Resets in 9 days", the date on hover
daily{ label: string; value: number | null }[]Usage per day for the sparkline; null for days still to come, shown as flat stubs
onUpgrade(state: "ok" | "warn" | "limit") => voidCalled by the button with the current state
titlestring"Usage"Card title, e.g. the workspace or product name
planstringPlan name in front of the reset date
unitstring"credits"Unit of the main meter
warnAtnumber0.8Share (0–1) from which the meter and the limits warn
upgradeLabelstring"Upgrade"Button text while there is room left (a quiet button)
topUpLabelstring"Add credits"Button text near and at the limit (a solid button)
classNamestringExtra class on the root

Keyboard

  • Tab: Reaches the period switch, the bar, the sparkline and the button
  • ← / →: Switch: picks the previous or next period. Bar: steps through the categories. Sparkline: steps through the days
  • Esc: Hides the category or day readout

Accessibility

  • A section labelled by its title; the bar and every limit are meters with their value as text ("7,420 of 10,000 credits")
  • The period switch is a radio group with a roving tab stop
  • The category tooltip is a polite live region, so stepping through the bar reads each part
  • Running low and reaching the limit are announced in a polite live region; amber is never the only signal – the text says it too

Responsive

  • Fills its container up to 460px; at 400px and below the switch gets its own row and the limits stack as rows
  • Works down to 320px wide; long labels end with an ellipsis, numbers and units never split
  • Touch: tapping a part or a legend row shows its value, targets grow to 36–38px
  • Dark mode built in: switches when an ancestor (usually <html>) has the class dark or data-theme="dark"

Requirements

  • React 19

Related

  • KPI Chart – Metric card with a multi-line chart and a tooltip that follows the pointer
  • Breakdown Chart – Stacked bar chart with measure, slice and segment pickers, a hover breakdown and a linked table
  • Billing Schedule – Build a billing phase – start date, duration, products with repeats and timing, discount, minimum and live totals
  • Progress Tracker – Onboarding checklist with an animated progress bar, ticks that draw themselves and auto play

Guides