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—accountingandstatementmodes, and the reserved paren columnDelta— the polarity contract, and the reserved sign slotStatementRowandStatementTable— negatives and rule lines togetherVarianceCell— 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