Skip to content
Neon UI
Esc
navigateopen⌘Jpreview
On this page

UsagePanel

Per-project consumption grid with plan gating and metering-lag honesty.

UsagePanel is the workspace’s usage tab: a grid of MetricCards over the v2 per-project consumption metrics — compute as CU-hours, storage and egress as bytes — under a mono header with the billing period — or a DateRangePicker in the filter slot when the range should be the user’s call. Cards enter one at a time in metric order. A plan-gated metric keeps its card frame but holds its voice: mono “available on a paid plan” with a quiet upgrade pill — an invitation, not a wall. And when metering trails real time, the footer says exactly how far, because the numbers shouldn’t pretend to be live.

usage

Compute
+11%over this period
21.4CU-hrs
Storage
10%over this period
2.9 GB
Egress
0%over this period
709 MB

Branch compute

available on a paid plan

metered through 21:40 UTC · ~15m behind

Install

npx shadcn@latest add https://ui.neon.com/r/usage-panel.json

Usage

import { UsagePanel } from "@/components/neon-ui/usage-panel";

<UsagePanel
  metrics={[
    {
      id: "compute_unit_seconds",
      label: "Compute",
      value: 22.3,
      unit: "CU-hrs",
    },
    {
      id: "storage_bytes",
      label: "Storage",
      value: 3_380_000_000,
      format: "bytes",
    },
    {
      id: "egress_bytes",
      label: "Egress",
      value: 731_000_000,
      format: "bytes",
    },
    {
      id: "branch_compute",
      label: "Branch compute",
      value: 0,
      gated: plan === "free",
    },
  ]}
  period="Jul 1 – Jul 18"
  meteredThrough="metered through 21:40 UTC · ~15m behind"
  onUpgrade={() => openUpgrade()}
/>;

Each metric accepts the full MetricCard surface: delta, comparisonLabel, trend, format, unit.

Wiring to Neon

One consumption call fills every card. Sum the billing period’s timeframes into a metric each:

import { createNeonClient } from "@/lib/neon-client";

const neon = createNeonClient(process.env.NEON_API_KEY);
const { data } = await neon.consumption
  .perProject({
    from: periodStart.toISOString(),
    to: now.toISOString(),
    granularity: "daily",
    project_ids: [projectId],
  })
  .all();

const timeframes =
  data?.[0]?.periods.flatMap((period) => period.consumption) ?? [];
const computeSeconds = timeframes.reduce(
  (sum, frame) => sum + frame.compute_time_seconds,
  0
);
// storage: synthetic_storage_size_bytes, writes: written_data_bytes

Compute arrives in seconds, so divide by 3600 for CU-hours; storage and writes are bytes, which format: "bytes" renders. Storage is a point-in-time size rather than a total, so read the newest timeframe instead of summing. Network egress is not in this response: use neon.consumption.perProjectV2 with the public_network_transfer_bytes metric when you need an egress card. Neon meters on a delay, so state it in meteredThrough rather than letting a low number read as a bug. Gate a metric with gated when the account’s plan does not include it.

States

  • Gatedgated: true swaps the value for the plan gate inside the same frame; onUpgrade renders the pill.
  • Metering lagmeteredThrough renders the mono footer notice.
  • LoadingisLoading skeletons every card through MetricCard.
  • Errorerror renders the house error line above the grid while cards hold their last values.

Props

PropType
metricsUsageMetric[]

Display order; id, label, value plus the MetricCard surface and an optional gated flag.

TypeUsageMetric[]
period?string

Billing period readout, e.g. "Jul 1 – Jul 18".

Typestring
filter?ReactNode

Header filter slot, e.g. a DateRangePicker; takes the period readout's place.

TypeReactNode
meteredThrough?string

Metering-lag notice rendered as a quiet mono footer.

Typestring
onUpgrade?() => void

Renders the upgrade action on gated metrics.

Type() => void
isLoading?boolean
Typeboolean
Defaultfalse
error?string | null

Panel-level failure rendered above the grid.

Typestring | null
footer?ReactNode

Extra content after the lag notice, e.g. an invoices link.

TypeReactNode

Accessibility

  • The panel is a section with a heading; the grid is plain flow content — cards carry their own labels.
  • Gated cards state the constraint in text; the upgrade action is a real button.
  • The error line is role="alert"; loading states ride MetricCard’s aria-busy.
  • Entrance stagger is static under prefers-reduced-motion.