Installation
npx shadcn@latest add @pixeldosa/cost-estimateRequires `cn` at `@/lib/utils` and @pixeldosa/confidence-meter, which installs automatically. Format `cost`, `duration` and `balance` yourself: the component never formats currency or time.
Usage
import { CostEstimate } from "@/components/ui/cost-estimate";
<CostEstimate
task="Research the top 5 competitors"
cost={{ low: "$1.10", high: "$1.80" }}
duration="4 to 6 minutes"
confidence="medium"
basis="your last 20 runs"
balance={{ label: "$8.20 of $50 left this month", used: 0.82 }}
onRun={start}
onCancel={dismiss}
/>
You format every figure. The component never guesses a currency, a credit unit or a duration.
Why it exists
People decide whether to let an agent run on two numbers nobody usually shows them: money and time. Showing them turns "just try it and see" into a decision, and it is the honest moment to admit an estimate is an estimate.
Features
- A range, not a figure.
costtakeslowand an optionalhigh. Passing onlylowis a deliberate claim that you know the price. - Confidence in tiers. Reuses
Confidence Meter, never a percentage. - The basis is named. "Based on your last 20 runs" lets someone judge the number instead of taking it on faith.
- A breakdown in words. A short list of what the spend is made of: pages read, images generated. No chart.
- Budget stated before the button. With
balance.exceeds, the component says the run would go over the budget, in the blocked colour, above the actions. - Holds completely still. It's your turn, and this one is about money.
Variants
With a breakdown and a budget
Before this runs
Generate 40 product images
Estimated cost480 credits to 600 credits
Estimated timeabout 8 minutes
- 40 images at 1024×1024400 credits
- Upscaling the 10 you keepOnly if you upscale80 to 200 credits
1,250 credits left
Over budget
Before this runs
Re-run the full catalogue
Estimated cost$62 to $78
Estimated timeabout 40 minutes
$18.40 of $50 left this month · this run would go over it
A single figure you're sure of
Before this runs
Summarise this thread
Estimated cost2 credits
Estimated timea few seconds
Props
| Prop | Type | Notes |
|---|---|---|
cost | { low: string; high?: string } | Required, formatted by you. Omit high only when the price is known. |
task | string | What the run is, in the user's words. |
duration | string | Plain words: "4 to 6 minutes". |
confidence | "low" | "medium" | "high" | Renders Confidence Meter. |
basis | string | Where the estimate comes from. |
items | { id, label, amount?, detail? }[] | What the spend is made of. |
balance | { label, used?, exceeds? } | Budget or credits left. used is measured, 0–1. |
onRun / runLabel | () => void / string | Defaults to "Run it". |
onAdjust / adjustLabel | () => void / string | For "Narrow the scope". |
onCancel | () => void | |
title | string | Defaults to "Before this runs". |
headingLevel | 2 | 3 | 4 | 5 | 6 | Defaults to 3. |
Accessibility
- A labelled
<section>with a real heading, so it fits your page outline. - Every state is in words. The over-budget warning is a sentence, not a colour.
- The budget bar is decorative and hidden; its label carries the same information.
- Nothing animates, so there is nothing to pause.
When not to use
- During a run. Live spend belongs to a meter beside the run, not to this.
- On a billing page. This is a decision surface, not an invoice.
- For token counts. Tokens aren't a unit people can judge. Convert to money, credits or time first.
why it's built this way
Engineering Notes
The API refuses the two lies this surface usually tells. First, a single figure: `cost` is a range, and a caller who genuinely knows the price passes only `low`, which makes precision an explicit choice rather than the default. Second, a percentage of certainty: confidence reuses the shipped three-tier Confidence Meter instead of inventing a number, and `basis` names the evidence so the estimate can be judged. Everything arrives pre-formatted as strings because currency, locale and credit units belong to the host product; a component that formatted money would have to guess a currency and would be wrong in half the places it renders. The only proportion drawn is the budget bar, which comes from a measured `used` value — the same rule the rest of the system applies to progress. `exceeds` is a separate flag rather than something derived from the numbers, because only the caller knows whether the estimate's high end, the low end, or an average is the figure that counts against a budget.
how it moves, and when it doesn't
Motion Notes
Nothing moves. This is the user's turn in the fullest sense — a decision about money — so the component holds completely still, with no shimmer, pulse or progress. The only transitions are the standard instant colour changes on its buttons, and those are disabled under `prefers-reduced-motion`.