Sum Check

Re-adds rows in exact arithmetic and points at the likely slip when a total doesn't match, or says it's only rounding.

StopsTotal doesn't add up

Installation

pnpm dlx shadcn@latest add https://bollard-ui.vercel.app/r/sum-check.json

The files land in your project. They're yours to edit.

Usage

sum-check-usage.tsx
import { checkSum, SumCheck, type SumCheckKind, type SumRow } from "@/components/ui/sum-check"

<SumCheck
  key={tab}
  rows={example.rows}
  kind={example.kind}
  total={total}
  roundedTo={example.roundedTo}
  format={example.kind === "percent" ? undefined : money}
  totalLabel={example.kind === "total" ? "Stated subtotal" : undefined}
  onSuspectSelect={flash}
  onUseSum={(sum) => setTotals((t) => ({ ...t, [tab]: sum }))}
/>

What it catches

  • Totals that don't equal the sum of rows
  • Gaps divisible by 9 (transposed digits)
  • A shifted decimal point, flipped sign or wrong side
  • A row left out or counted twice
  • Gaps explained by rounding when decimals are set

Props

SumCheck props
PropTypeDefault
kindSumCheckKind"total"
total

The stated total ("total"), or the target for "percent" (default 100).

number—
roundedTo

Decimal places the row values were rounded to. Set it and small gaps are reported as rounding.

number—
rows* (required)

Rows to add, and the stated total or debit/credit rows.

SumRow[]—
format

Formats amounts. Defaults to the row precision; percent adds "%".

(value: number) => string—
onUseSum

Offer a one-tap "use the row sum" fix for a wrong stated total.

(sum: number) => void—
onSuspectSelect

Called when a likely culprit is picked, so the table can scroll to and highlight that row.

(id: string) => void—
sumLabel

Label of the computed sum.

string"Sum of rows"
totalLabel

Label of the stated total.

stringkind === "percent" ? "Should add up to" : "Stated total"
debitsLabel

Label of the debits sum.

string"Debits"
creditsLabel

Label of the credits sum.

string"Credits"
okText

Templates: `{gap}`, `{sum}`, `{total}`, `{higher}`, `{tolerance}`, `{count}`, `{decimals}` are filled in.

stringkind === "balance" ? "Debits equal credits." : "Adds up."
offText

Shown when the total is off. `{diff}` is filled in.

stringkind === "balance" ? "Off by {gap}: {higher} are higher."…
roundingText

Shown when the gap is only rounding.

string"Off by {gap}, which rounding alone explains: {count} fig…
ninesText

Hint when the gap points to transposed digits.

string"The gap divides by 9, a typical sign of two swapped digi…
suspectsLabel

Accessible label of the suspects list.

string"Likely cause"
useSumLabel

Label of the use-the-sum fix.

string"Use {sum}"
wholeNumbersText

The `{decimals}` in `roundingText`: whole numbers, or `{places}` decimal places.

string"whole numbers"
decimalPlacesText

Explains decimal places used for rounding.

string"{places} decimal places"
reasons

Text for each kind of suspected slip.

Partial<Record<SumSuspect["reason"], string>>—

* required. Also accepts the native props of <div> (className, aria-*, data-*…), passed through to it.

Accessibility

  • Status in a polite live region; focus moves to it after a fix
  • Suspects list has an aria-label; each suspect is a button
  • Rolling figures respect reduced motion

Only what the source does. Test with your own screen reader and content before shipping.

Limitations

  • Suggests single-row slips only; several errors at once may not be explained
  • Rounding tolerance depends on the decimals you set

What it stops

Total doesn't add up

Also known as

total · sum · reconciliation · invoice · ledger · spreadsheet · accounting · tally

Field type

Dependencies

Pairs well with