BankStatement
Opening → lines with a running balance → closing, proven against the bank's own figure.
npx shadcn add @lopifi/bank-statementExamples
The same component, case by case. Each stage is the shipped code with the props it shows; switch to Code for the usage.
How it behaves
What it renders, what it refuses to render, and why. The same paragraphs as the family spec, so the two can’t disagree.
The bank's statement, proved: opening → every line → closing, against the bank's own figure.
<BankStatement
institution="Fernhill Bank"
accountType="Business checking"
last4="4021"
period={{ start: new Date(2026, 8, 1), end: new Date(2026, 8, 30) }}
openingBalance={money(4_812_045)}
closingBalance={money(3_414_029)} // as the bank printed it
lines={lines} // date order, as printed
/>
type BankStatementLine = {
id: string;
date: Date;
description: string;
reference?: string;
/** Exactly one is set. Both unsigned: the column is the sign. */
withdrawal: MoneyValue | null;
deposit: MoneyValue | null;
/** The running balance the bank printed beside the line, if it printed one. */
balance?: MoneyValue | null;
};
Withdrawals and deposits are two columns, never one signed amount column, as debit and credit
are. The running balance is walked from the opening figure in minor units and is only a running
balance in date order, so the table declares data-sortable="false".
The proof sits below the lines, outside the scroller, so it never scrolls away: opening
balance · plus deposits · less withdrawals · = closing balance, computed · closing balance per
bank · difference. The labels carry the operation and the figures stay unsigned. A zero
difference closes on the double rule with a positive confirmation. A non-zero one turns the
border --negative and states that a line is missing or doubled.
Finding the line. Where the bank printed its own running balance, the first line at which the walk departs from it is marked, with the bank's figure in words beneath. That's where the missing or doubled line sits. Only the first is marked, because every line after it is off by the same amount and marking those too would bury the one that matters.
Per-currency decimals. One statement is one currency. Figures take their decimals from it (EUR 2, JPY 0, KWD 3), and nothing is ever summed across statements.
Responsive (table collapse):
- ≥ md: the table. Date frozen left, header frozen, description clamps at two lines,
figures never truncate, the scroller capped by
maxHeight. - < md: a list, not a sideways scroll. Description and amount lead. Date and reference sit beneath the description; the kind of movement ("Deposit", "Withdrawal") and the running balance sit beneath the amount. The kind is said in words, never carried by a sign.
Header: institution mark (EntityMark), account type, masked number, statement period,
currency: the basis of every figure below it.
Motion: none beyond the row hover tint (fine pointer only). A statement is a document.
The accounting it encodes
The concepts this component renders, each defined once in the lexicon.
More in Ledger & tables
See also
Where this component is specified, defined and running.
Elsewhere in the system
The live section BankStatement renders in, the calculators behind its figures, and the ledger payloads that feed it.
- BankStatement, runningThe tables showcase — live, with theme, density, grayscale and reduced-motion switches.
- Ledger & tablesThe table is the product. It gets the same design attention as the hero card. Specs for 17 React components: TransactionTable, TransactionRow, TransactionCard, TransactionDetail and 13 more.
Components are copied into your repository and meant to be edited. Rounding policy, fiscal calendar and compliance copy differ per company.
Release notes
Get an email when Ledger & tables changes
Your copy doesn’t update itself when we fix something here.
- One email when a Ledger & tables component needs a migration
- Nothing when nothing changes
Or install it now
npx shadcn add @lopifi/bank-statement