PixelDosa UI

Credits Meter

What's left, and what this action will cost — named together, before the click.

Credits

120 credits of 2,000 this month

4 images · 1024 × 102440 credits

Resets on 1 October · 12 refunded yesterday when a batch failed

Installation

bash
npx shadcn@latest add @pixeldosa/credits-meter

Requires `cn` at `@/lib/utils`. Keep `nextAction.label` specific to the click it sits beside: '8 images · 1024 × 1024', not 'Generate'.

Usage

import { CreditsMeter } from "@/components/ui/credits-meter";

<CreditsMeter
  remaining="120 credits"
  allowance="2,000 this month"
  used={0.94}
  low
  nextAction={{ label: "4 images · 1024 × 1024", cost: "40 credits", affordable: true }}
  resets="Resets on 1 October"
  recent="12 refunded yesterday when a batch failed"
  onTopUp={openBilling}
/>

Why it exists

Credit opacity is the first frustration buyers name in reviews of these tools. The specific complaint isn't the price, it's that you can't tell what an action will cost until it's already gone — and refunds, when they happen, are invisible.

Features

  • The action and its price are one object, so they can never be rendered apart.
  • "More than you have" is stated when the next action exceeds the balance, before the click.
  • Refunds have somewhere to appear. Pipelines refund routinely; interfaces rarely say so.
  • Expiry in plain words: "Resets on 1 October".
  • Measured only. The bar is drawn from a real used value, never an estimate.
  • The bar empties as the balance falls. You pass what's been spent; it draws what's left, so the graphic and the figure above it never say opposite things.

Variants

Enough left

Credits

1,250 credits of 2,000 this month

8 images · 1024 × 102480 credits

Resets on 1 October

Not enough for this action

Credits

20 credits of 2,000 this month

4 images · upscaled to 4K260 credits · more than you have

Resets on 1 October · 12 refunded yesterday when a batch failed

Props

PropTypeNotes
remainingstringRequired, formatted by you.
allowancestringThe full allowance, when there is one.
usednumberMeasured, 0–1, the share already spent. The bar draws the remainder.
nextAction{ label, cost, affordable? }The click this sits beside, and its price.
resetsstringWhen the balance renews or expires.
recentstringRecent movement, including refunds.
lowbooleanMarks a balance worth flagging.
onTopUp / topUpLabel() => void / string
labelstringDefaults to "Credits".

Accessibility

  • A labelled group; every figure is text, and the bar is decorative.
  • "More than you have" is a sentence, not only a colour.

When not to use

  • A billing page. This is a decision aid beside an action.
  • Raw token counts. Convert to the unit people are charged in.
  • A whole run's estimate before it starts: use Cost Estimate.

why it's built this way

Engineering Notes

`nextAction` is a single object rather than separate label and cost props so the two can never be rendered apart: a price with no action, or an action with no price, is the pattern the component exists to replace. `affordable` is a caller flag instead of something derived from parsing the strings, because balances and costs arrive as formatted text in whatever unit and locale the product uses, and a component that tried to compare them would have to parse money. The bar is drawn only from a measured `used` value, which keeps it consistent with the rest of the system's rule that a proportion must be real, and it draws what is *left* rather than what is spent: it sits under a figure that counts down, and a bar filling up beside a balance running out reads as plenty-left at the exact moment it is not. Refunds get a place in `recent` because generation pipelines refund routinely and almost no interface shows it, which leaves people believing failures cost them money.

how it moves, and when it doesn't

Motion Notes

Nothing animates. A balance is a fact and the next action's price is a decision point, so neither has anything to express through motion; only the standard instant colour change on the top-up button, disabled under `prefers-reduced-motion`.