Account Balances

The user's money across connected accounts at a glance, with a net total, an allocation bar and each account's balance.

Experience · Shares patterns with KPI Card, Insight Card

≈ 1,479 tokens per use (tool 1,003 · guidance 362 · typical payload 114)How token costs are estimated

Example: Account Balances

What's my total balance?

Total balance

$56,354.94

Up +3.12%This month

  • USDT Wallet 60%
  • Bank account 40%
  • USDT WalletLantern Wallet · 0x3FD…9B12
    $33,812.96
  • Bank accountHarbor Bank · GB12…25
    $22,541.98

When to use

  • The user asks about their money across connected accounts: "What's my total balance?", "How much is in each account?", "How's my net worth this month?"
  • The agent has the balances from a connected banking, brokerage or wallet tool.

When not to use

  • The agent doesn't have the balances from a tool result. Never invent or estimate them.
  • The user asks about transactions or spending. Use a Data Table, a Chart or a KPI Card.
  • The user wants to move money, pay or trade. This card has no such actions; confirm those through the host's own flow.
  • It's one account's balance. Say it in a sentence.

How the agent should use it

  • Pass every balance exactly as the source returned it, and let the component add them up. Only pass total when the source reports one.
  • Mark credit cards and loans with kind credit or loan. They're listed under Liabilities and subtracted from the total, never drawn in the allocation bar.
  • For an account in another currency, pass currency and the source's converted balanceInDisplay. Never convert with a rate you made up.
  • Pass mask only as the source already masked it, never a full account number, IBAN or wallet address.
  • Set hideAmounts when the user asks to hide amounts or is sharing their screen.
  • Add reconnect to actions only when an account has an error and the host can reconnect it.

Totals

The total is net worth: assets minus what's owed on liabilities, computed in integer minor units so the cents never drift. When there are liabilities, they're listed under Liabilities as negative amounts, and screen readers also hear "Net of $2,140 owed" with the total.

  • Liabilities (credit, loan) are read as the amount owed, whichever sign the source uses, and shown as negative.
  • Other currencies count only with balanceInDisplay. The row shows the converted balance with the original underneath ("€20,712.40"). Without it the account shows in its own currency, is marked "Not in total", and a note under the total says "Excludes 1 unconverted".
  • A stated total is shown as given. If it's more than 0.01 away from the accounts, a note says "Total differs from accounts" instead of hiding the mismatch.

Cents are dimmed by splitting Intl's formatToParts, so it works for any currency and locale: $56,354.94, 20.712,40 €, or ¥1,234,567 with no cents at all.

Allocation bar

Segments are proportional to each asset account's share of assets (positive balances only), drawn with the neutral chart scale in account order. The bar has no labels: the same swatch sits beside each account's name in the list. Screen readers get the shares as a list ("USDT Wallet 60%, Bank account 40%"), rounded with largest remainders so they add up to 100%.

Privacy

  • Masking is defensive. Whatever arrives in mask, the component never shows more than the first 4 and last 4 characters, with "…" between (a 0x prefix doesn't count). GB12 2381…25 shows as GB12…25; a raw 12345678 as …5678.
  • Hide amounts replaces every amount with $•••• (the currency symbol stays). The eye button is a toggle (aria-pressed, labelled "Hide amounts"), and screen readers hear "hidden" instead of the figure. hideAmounts sets the starting state; the user's toggle wins.

Status and freshness

  • Accounts with an error show it with a warning icon, plus a Reconnect button when reconnect is in actions.
  • Balances older than 24 hours add "Updated 3 days ago" to the account's quiet line, after the institution and mask. It's computed after mount, so server and client render the same markup. Pass now to compare with a fixed moment instead.
  • The change line ("+3.12% This month") uses an arrow and a sign as well as green or red, so it never relies on color alone.

Streaming

The header arrives first. The total stays a skeleton until every account is in, so it never shows a running total that jumps; accounts appear one by one.

Installation

npx shadcn@latest add https://your-site.com/r/account-balances.json

Usage

app/api/chat/route.ts
import { tool } from "ai"
import { accountBalancesTool } from "@/components/genui/account-balances.schema"

const tools = {
  [accountBalancesTool.name]: tool({
    description: accountBalancesTool.description,
    inputSchema: accountBalancesTool.inputSchema,
    // The card answers the question, so the tool can return immediately.
    execute: async () => ({ shown: true }),
  }),
}

Every button is a handoff to your app. Map results to your own screens:

<AccountBalances
  {...part.input}
  status={part.state === "input-streaming" ? "streaming" : "ready"}
  onResult={(r) => {
    if (r.action === "add-account") openAddAccount()
    if (r.action === "view-details") openAccounts()
    if (r.action === "reconnect") openReconnect(r.accountId)
  }}
/>

The result is one of:

{ action: "add-account" }
{ action: "view-details" }
{ action: "reconnect", accountId: "savings" }

With LangGraph

Push it from a graph node with ui.push({ name: "show_balances", props: { toolCallId, ...args } }, { message }), and render the thread's UI messages with the Renderer.

Restoring from history

There's no finished state to restore. Render the stored tool input again; the card stays live.

Schema

PropTypeNotes
titlestring?Default "Total balance"
currencystringISO 4217 code every total is shown in
totalnumber?As the source reports it. Computed from the accounts when omitted. Data-bound
change{ percent?, amount?, period }?percent in percent units (3.12 = +3.12%). Data-bound
accountsAccount[]1–20. Data-bound
hideAmountsbooleanStart masked. Default false
actions("add-account" | "view-details" | "reconnect")[]?Default ["add-account", "view-details"]
localestring?BCP 47 locale for numbers

Account:

FieldTypeNotes
idstringReturned with reconnect
namestring
kind"checking" | "savings" | "credit" | "investment" | "crypto" | "cash" | "loan" | "other"credit and loan are liabilities
institutionstring?
maskstring?Already masked by the source; clipped to first 4 and last 4
balancenumberIn the account's currency. For liabilities, the amount owed
currencystring?When it differs from the display currency
balanceInDisplaynumber?Converted by the source. Required when currency differs
asOfstring?ISO 8601. Older than 24 hours shows "Updated …"
errorstring?e.g. "Reconnect needed"

Host props: status (streaming or ready), now, onResult and className.

Source

On this page