Data typography
Most "this table is hard to read" problems are typographic, and most of them are three fixes: tabular figures, right alignment, and consistent precision.
Tabular figures — the single highest-value line of CSS
By default most fonts use proportional digits: a 1 is narrower than a 0. In a column of numbers,
digits fail to stack and the eye cannot compare magnitudes by length.
.num, td, th, .figure {
font-variant-numeric: tabular-nums;
font-feature-settings: "tnum" 1; /* fallback for older engines */
}
Apply it to every numeric context: tables, KPI figures, axis ticks, data labels, tooltips.
Alignment rules
| Content | Alignment | Why |
|---|---|---|
| Numbers | Right | Magnitude is comparable by digit position |
| Text labels | Left | Reading order |
| Column headers | Match their column | A left-aligned header over right-aligned numbers looks broken |
| Units and currency | Attached to the number, not a separate column | Splitting them doubles the eye's work |
| Dates | Left, in a fixed format | 2026-08-19 sorts and aligns; Aug 19, 2026 does neither |
Align a numeric column on the decimal point by giving every cell the same decimal places. Ragged precision within a column is the most common cause of a column that will not scan.
Precision — decided per column, never per cell
State the rule once, apply it to the whole column:
| Context | Precision |
|---|---|
| Currency in a summary | Whole units ($4,820,115) |
| Currency in a reconciliation | Two decimals — cents matter, and the exact match is the point |
| Compact axis / KPI | One decimal ($4.8M) |
| Percentages | One decimal (104.2%); zero when the input is coarse |
| Ratios | One or two decimals, and only if the inputs support it |
| Counts | Zero, always |
Never show more precision than the number has. A figure derived from a rounded extract displayed
to the cent is a false accuracy claim. See ui-antipatterns.
Pin the unit for a whole axis or column. Mixing $1.2M and $900K forces unit arithmetic on the
reader. Choose M and accept $0.9M.
Negatives
Two conventions. Pick per context; never mix within one view.
| Convention | Use in |
|---|---|
Parentheses (1,234.50) |
Tables, statements — the accounting convention your audience expects |
Minus sign −1,234.50 |
Charts, labels, compact displays — readable at small sizes |
Use a true minus (−, U+2212) rather than a hyphen in display type; a hyphen is too short and sits
too high against digits.
Colour may reinforce sign but must never be the only encoding — it fails in greyscale and for colourblind readers. The parenthesis or the minus is the encoding; colour is a bonus.
Type scale
A small, fixed scale. Data pages need fewer sizes than editorial ones.
--text-xs: 11px; /* footnotes, provenance, axis ticks */
--text-sm: 13px; /* table body, secondary labels */
--text-base: 15px; /* body */
--text-lg: 18px; /* section headings */
--text-xl: 24px; /* supporting figures */
--text-2xl: 36px; /* the one headline figure */
Two rules:
- Weight before size. Going 400 → 600 differentiates without disturbing the layout. Reach for size only when weight is not enough.
- Two weights, maybe three. 400 and 600 covers nearly everything; add 500 only if you need a middle step.
The label/value pair
The atom of a data interface. Get it right once and reuse everywhere.
NET NEW ARR ← label: small, muted, uppercase or sentence case, never bold
$210K ← value: large, high contrast, tabular
▼ 27.6% vs plan ← context: small, with direction encoded by glyph AND colour
- The label is subordinate. A bold uppercase label competing with its value is backwards.
- The value carries the weight. It is what the reader came for.
- The context line is optional and must add something — "vs plan", "vs prior", "TTM". A context line that just repeats the period is noise.
- Keep the vertical gap inside the pair tighter than the gap between pairs (
visual-hierarchy).
Fonts under a strict CSP
Web fonts require an external host and are blocked; embedding one as base64 costs real page budget. A system stack costs nothing and renders natively everywhere:
--font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
"Helvetica Neue", Arial, sans-serif;
--font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
"Liberation Mono", monospace;
Use the mono stack for identifiers — transaction ids, account codes, hashes — never for figures.
Monospace digits are wider and less legible than a good sans with tnum enabled.
Line length and leading
- Prose: 60-75 characters. A full-width paragraph in a wide dashboard is unreadable.
- Body line-height: 1.5. Table rows: 1.3-1.4 — tighter, since rows are short.
- Large figures: 1.1. Default leading around a 36px number leaves an awkward gap.
Related skills
visual-hierarchy— where these elements sitdesign-tokens— the scale as tokensfinancial-tables— applying all of this to a real tableui-antipatterns— false precision and the failure modesartifact-accessibility— contrast and minimum sizes