Skip to content

Negative numbers in a financial table

Parentheses instead of a minus sign, the real minus where a sign is right, and the column that stays aligned when the sign changes. Three rules, one of which almost nobody implements.


Which convention, where

Context Format Why
Statements, ledgers, tables (1,234.00) The accounting standard. The reader is scanning a column, and parentheses are unmissable in peripheral vision in a way a leading hyphen is not.
Deltas, KPIs, inline prose −$1,234.00 A delta is a change, not a balance. In isolation an explicit sign reads faster than a bracket.

Both are coloured --negative, and neither depends on colour — the parens and the sign are the primary signal, which is what makes the interface survive a greyscale print and a red-green colour deficiency. You can check that claim yourself: the showcase has a greyscale toggle in its header.

Do not mix the two conventions inside one table. A column where some negatives are bracketed and some are signed reads as two different measurements.

Use the real minus

− is U+2212 MINUS SIGN. - is U+002D HYPHEN-MINUS, and it is a different glyph: shorter, lower, and a different width from +. In a column of deltas, that width difference means +12.4% and -12.4% do not line up.

Intl does not save you here: en-US and most locales emit U+002D, and only a few (Swedish, Finnish and others) emit U+2212. Swap the sign after formatting, or use currencySign: "accounting" and parentheses. The same bug arrives when someone hand-builds the string:

`-${format(Math.abs(value))}`   // hyphen-minus. Wrong width, breaks the column.

The alignment problem

This is the one that gets skipped. In a right-aligned column mixing 1,234.00 and (1,234.00), the closing paren pushes the digits about 4px left, and the decimal points stop lining up down the column. The effect is subtle enough to survive review and obvious enough to look amateur next to a printed statement.

Three fixes, in order of preference:

1. Reserve the paren column. Render an invisible ) on positive values.

<span aria-hidden className="invisible">)</span>

Cheapest, works everywhere, and keeps copy-paste clean as long as you strip aria-hidden content on copy.

2. Grid the cell. display: grid; grid-template-columns: auto 1fr auto; — open paren, figure, close paren. The most robust option for mixed content.

3. Pad the cell. padding-right: 1ch on positives. Only correct under font-variant-numeric: tabular-nums, where 1ch is a real fixed unit.

Give the sign glyph in a delta column the same treatment: reserve the slot, so a list of changes stays aligned even when some rows are unchanged.

Sign is not sentiment

A negative number is not bad news. A −4.2% change in burn is good; a −4.2% change in revenue is not. Colouring by sign is how a dashboard reports cheerful green on a cost overrun.

So polarity is a prop, and it is explicit:

<Delta value={{ amount: 8200, currency: "USD" }} polarity="inverted" />
// ↑ +$8,200.00 — coloured negative, because up is bad for this metric

standard | inverted | neutral, stated per metric. There is no defensible default, so the component does not pick one for you.

Zero is not a small negative

A true zero in a statement renders as an em dash — — — which says computed, and nil. A blank cell says not applicable here. n/a says we do not have this. Three states, three glyphs, and none of them is $0.00. (In transactional tables and inputs, where a figure is typed rather than read as a total, $0.00 is fine — see formatting money.)


Where this is implemented

  • Money — accounting and statement modes, and the reserved paren column
  • Delta — the polarity contract, and the reserved sign slot
  • StatementRow and StatementTable — negatives and rule lines together
  • VarianceCell — where a signed number and a polarity meet a budget
  • Statements, running — flip a value negative and watch the column hold
  • Numerics — the full rule, with the three fixes ranked
  • Colour — why the sign never depends on hue alone

Told when this spec changes

The docs are the contract the components are built against. When one changes, the component changes with it — and the copy in your repo does not. One email when that happens.

One address, no list rental, unsubscribe in one click.

Works in your QuickBooks Online or Xero file

Get your books closed every month

Lopifi is building AI bookkeeping that keeps your books closed on US GAAP as transactions happen, with a CPA review every quarter, for $200 a month. We’re not open yet.

$200/month when we open. No card now.