URL: https://wattfare.com/docs/guides/budget-ui
# Budget UI patterns

> The budget is the part of your product users feel. These patterns — a clear connect button, a live budget bar, depletion warnings, and an obvious disconnect — turn “AI billing” into something users trust.

## The connect button

The first impression. Make it unmistakable and reassuring: a prominent CTA, the voltage bolt for recognition, and a disabled/loading state while the popup is open.

```tsx
function ConnectButton() {
  const { connect, loading } = useWattfare();
  return (
    <button onClick={connect} disabled={loading} className="wf-connect">
      <BoltIcon />
      {loading ? "Connecting…" : "Connect AI budget"}
    </button>
  );
}
```

> **Set expectations in the label**
>
> “Connect AI budget” reads better than “Sign in” or “Authorize.” Users are about to set a spending cap — name the thing they control.

## The budget bar

Once connected, show remaining budget inline — ideally somewhere persistent like a header. Drive the fill from `usage` vs. `limits`, and flag a low state early.

```tsx
function BudgetBar() {
  const { connected, status, remainingUsd } = useWattfare();
  if (!connected) return null;

  const cap  = status.limits?.monthlyUsd ?? null;
  const used = status.usage.monthlyUsd;
  const pct  = cap ? Math.min((used / cap) * 100, 100) : 0;
  const low  = remainingUsd !== null && remainingUsd < 1;

  return (
    <div className={`budget-bar${low ? " is-low" : ""}`}>
      <div className="budget-track">
        <div className="budget-fill" style={{ width: `${pct}%` }} />
      </div>
      <span className="budget-label">
        {remainingUsd !== null ? `$${remainingUsd.toFixed(2)} left` : "Unlimited"}
        {low && <em> · running low</em>}
      </span>
    </div>
  );
}
```

> **Keep it fresh**
>
> `remainingUsd` only updates when you call `refresh()`. Refresh after each inference request (e.g. AI SDK's `onFinish`) so the bar tracks reality.

## Depletion: warn, then gate

Two thresholds, two behaviours:

- **Low (e.g. < $1 left)** — a soft inline warning. Don't block anything yet.
- **Empty (≤ $0)** — disable the input and explain why, with a link to raise the cap. A disabled field with a reason beats a request that fails after the user hits enter.

```tsx
function ChatInput() {
  const { connected, remainingUsd } = useWattfare();
  const depleted = remainingUsd !== null && remainingUsd <= 0;

  if (!connected) return <ConnectButton />;

  return (
    <form>
      <input disabled={depleted} placeholder={depleted ? "Budget used up for this month" : "Ask anything…"} />
      {depleted && (
        <p className="hint">
          You've hit your monthly cap. <a href="/account">Raise it</a> to keep going.
        </p>
      )}
    </form>
  );
}
```

## Always offer disconnect

Control is the whole pitch. Give users an obvious way to disconnect and to see what they've spent. It costs you nothing and it's the difference between “this app can spend my money” and “I let this app spend my money, and I can stop it.”

```tsx
function BudgetMenu() {
  const { connected, disconnect, status } = useWattfare();
  if (!connected) return null;
  return (
    <details className="budget-menu">
      <summary>Budget · {status.usage.monthlyUsd.toFixed(2){"}"} used</summary>
      <a href="/account">Manage budget</a>
      <button onClick={disconnect}>Disconnect this app</button>
    </details>
  );
}
```

## A trust checklist

- **Show the number.** Always display remaining budget once connected — never hide it.
- **Warn before the wall.** A low-budget hint prevents the surprise of a hard stop.
- **Explain the block.** When gated, say why and link the fix — don't just disable silently.
- **Make leaving easy.** A visible disconnect builds more trust than any copy could.
- **Mirror the cap.** The number you request in `sessionHandler` is what users approve — keep your UI honest about it.

> **Tip**
>
> Pair this with [Error recovery](https://wattfare.com/docs/guides/error-recovery): the budget bar handles the happy path, and the error states handle the moment a call is refused.
