Super-AI-Components

Usage Dashboard

Aggregate cost / token / latency

Usage

Total spend
128.7 USD+12% vs previous period
Total tokens
412,000-4% vs previous period
Avg latency
830ms±0% vs previous period
Per-model breakdown
Where the spend in this period went, model by model.
Spend, tokens, and latency by model for the selected period
ModelSpendTokensLatency
gpt-4o78.4 USD236,000960ms
gpt-4o-mini34.9 USD141,000580ms
claude-3-5-haiku15.4 USD35,000610ms

What it is

The team-facing view of what an AI product costs to run: a period selector, a row of summary figures (spend, tokens, average latency) each paired with a change from the prior period, and a per-model breakdown that says where the spend actually went. It's the counterpart to M2 credits-indicator — same underlying data, aimed at whoever owns the bill instead of the one user watching a balance.

Why it matters

Provider consoles converge on this exact shape because a raw total is diagnosis without treatment: it tells a team spend went up, but not which model, endpoint, or feature to look at. Breaking the same numbers out by model is what turns a dashboard into something actionable instead of a bill you just look at.

Observed in: Provider consoles

Anatomy

  1. 1usage-dashboardRoot wrapper around the whole view.
  2. 2usage-dashboard-headerTitle plus the period selector.
  3. 3usage-dashboard-titleThe dashboard's own heading, inside the header row.
  4. 4usage-dashboard-period-triggerThe period Select's trigger — always shows the period's label, never its id.
  5. 5usage-dashboard-period-contentThe open Select's popup listing every period.
  6. 6usage-dashboard-period-itemOne selectable period row inside the popup.
  7. 7usage-dashboard-summaryThe row of summary cards: spend, tokens, latency.
  8. 8usage-dashboard-summary-cardOne summary figure and its delta, keyed by data-metric.
  9. 9usage-dashboard-summary-valueThe headline number inside a summary card.
  10. 10usage-dashboard-deltaSigned percentage vs. the prior period, stated as text with a directional icon.
  11. 11usage-dashboard-model-breakdownThe per-model panel — a full-size Card, heavier than the summary row.
  12. 12usage-dashboard-model-chartDecorative bar chart, aria-hidden — every number it shows also exists in the table beside it.
  13. 13usage-dashboard-model-tableThe accessible source of truth: one row per model, spend/tokens/latency as real text.
  14. 14usage-dashboard-model-rowOne model's table row, keyed by data-model-id.

How to use it

Reach for it wherever a team (not an individual end user) needs to see what an AI feature is costing to run. Pass `periods` and a `data` map keyed by each period's id; switching the period is a single interaction that moves the summary row and the model breakdown together, because they're reading the same underlying period record. Include `spendDeltaPct`/`tokensDeltaPct`/`latencyDeltaPct` on the summary whenever you have a prior period to compare against — a figure with no delta reads as a number that hasn't been checked yet, not as a considered omission.

Do

  • Let the period selector move every panel at once — the summary cards and the model breakdown read the same period record, so there's no separate 'apply' step.

    Usage

    Total spend
    68.4 credits+9% vs previous period
    Total tokens
    184,000-3% vs previous period
    Avg latency
    720ms±0% vs previous period
    Per-model breakdown
    Where the spend in this period went, model by model.
    Spend, tokens, and latency by model for the selected period
    ModelSpendTokensLatency
    gpt-4o-mini44.1 credits128,000610ms
    claude-3-5-haiku24.3 credits56,000840ms
  • State every delta as text — a signed percentage and a comparison phrase, plus a directional icon — never colour alone.

    Usage

    Total spend
    96.4 credits+24% vs previous period
    Total tokens
    258,000-9% vs previous period
    Avg latency
    910ms±0% vs previous period
    Per-model breakdown
    Where the spend in this period went, model by model.
    Spend, tokens, and latency by model for the selected period
    ModelSpendTokensLatency
    gpt-4o96.4 credits258,000910ms

Don't

  • Don't ship the total without the per-model breakdown — a total says there's a problem, the breakdown says what to do about it.

    Total spend

    512.30 USD

    (nothing else on the card — which model? which call?)

  • Don't key which model is which to a colour legend alone — name every model in real text next to its own numbers, the way the table does.

    Spend by model

    gpt-4ogpt-4o-minihaiku

Accessibility

Keyboard
  • One tab stop, whatever the data: the period Select's trigger. The summary cards are text, the bar chart is out of the tab order (`accessibilityLayer={false}` under an `aria-hidden` wrapper), and no table cell is focusable — a hundred models adds no stops.
  • On the trigger, Enter, Space, Down or Up opens the list; typing a letter jumps to a period, Home and End reach the ends, Enter or Space commits, and Escape closes without changing the period.
  • Nothing in the breakdown is operable. Columns do not sort, rows do not select, and there is no arrow-key movement inside the table — it is a static table, not a grid.
  • There is no `disabled` prop. The Select is the only control, and this component does not forward a disabled state to it, so freezing the dashboard means not rendering the selector at all.
Screen reader
  • The trigger's name comes from `aria-label`, not from what you can see: "Period: Last 7 days". The visible label is a substring of it, so voice control still reaches it — but pass a `period` id that is not in `periods` and the name degrades to a bare "Period: ".
  • A summary card is three unlinked strings. `CardTitle` renders a `<div>`, not a heading, and nothing associates it with the figure or the delta, so "Total spend", "1,240 credits" and "+12% vs previous period" are read as siblings joined only by reading order.
  • The bar chart contributes nothing at all — `aria-hidden` with the Recharts accessibility layer switched off, so it is out of both the accessibility tree and the tab order. Everything it draws is in the table beside it.
  • The table is real: a visually hidden `<caption>`, four `<th scope="col">`, and a `<th scope="row">` per model, so a spend cell announces with the model that owns it. Spend is a `CostChip` whose coins glyph is `aria-hidden` and whose amount and unit read as one string.
  • Delta icons are `aria-hidden`. The direction lives in the signed text and in `data-direction`, never in the colour — the `text-warning` tint on an increase is decoration on top of a sentence that already says it.
  • Changing the period is announced as nothing beyond the Select's own new value. All three figures and every table row are replaced with no live region anywhere in this component, and switching to a period with no models swaps the table for a plain `<p>` just as silently. Put a live region on the area you drop it into if the change has to be heard.
  • `title` renders as a fixed `<h3>` and is the component's only heading; pass a falsy `title` and there is none. "Per-model breakdown" and the three summary labels are `<div>`s, so this contributes exactly one heading, at one level you cannot choose, to your page outline.
Focus
  • Opening the period list moves focus into the popup; closing it — by choosing, by Escape, or by clicking away — returns focus to the trigger. Nothing else moves focus: the cards and the table are rebuilt underneath a focus that never left.
  • The trigger is the only control with a focus style, `focus-visible:ring-3` inherited from the vendored `SelectTrigger`. There is nothing else focusable to style.

Watch out for

  • Passing the period id straight into the Select's value display. The vendored Select renders the raw value unless SelectValue is given explicit children — this component always passes the period's own label, and a call site building a custom trigger around the same data should do the same.
  • Treating the bar chart as the only place model spend lives. It's aria-hidden on purpose — a screen reader user gets the same numbers from the table next to it, but only if that table stays in sync with whatever data feeds the chart.
  • Building three separate views for 'period select', 'summary cards', and 'model breakdown'. They're anatomy of one dashboard, not alternate states — a period change has to reach all three at once, which is only guaranteed if they share one component's state.

Installation

npx shadcn@latest add https://super-ai-components.vercel.app/r/usage-dashboard.json

Requires a shadcn app on Base UI (style base-nova). Radix-based styles such as new-york fail to typecheck: these components use Base UI's render prop and callbacks like onOpenChangeComplete, which Radix primitives do not have.