Financial tables
Finance audiences read tables. They will read a well-set table faster than any chart, because they know the shape of a P&L and they want the exact numbers.
Treat the table as a primary deliverable, not as a fallback. Most dashboards would be improved by one strong table and one chart, instead of six weak charts.
Non-negotiables
Three lines of CSS and one rule fix most unreadable tables:
table { border-collapse: collapse; width: 100%; }
td, th { font-variant-numeric: tabular-nums; } /* digits stack */
.num { text-align: right; } /* numbers right, labels left */
Plus: consistent decimal places within a column. Ragged precision is the single most common reason a numeric column will not scan.
Hierarchical statements
A P&L is a tree. Encode the hierarchy with indentation and weight, never with borders.
Revenue 4,820,115
Subscription 4,412,880
Services 407,235
──────────
Cost of revenue (1,156,828)
Hosting (612,400)
Support (544,428)
──────────
Gross profit 3,663,287 ← bold, rule above
Gross margin % 76.0% ← muted, derived
- Indent children (12-16px per level). Two levels is usually enough; three is the practical maximum.
- Subtotals in weight 600 with a rule above. The rule signals summation.
- Grand totals get a double rule — the accounting convention, and readers look for it.
- Derived metrics (margin %) sit muted beneath their line, so they read as commentary rather than as another line item.
- Negatives in parentheses in tables, per accounting convention (
data-typography). - Every subtotal must equal the sum of its children. If it does not because of exclusions or rounding, add a visible reconciling line rather than letting the reader find the gap.
Column order
Left to right, in the order the eye needs:
Line item │ Actual │ Prior │ Δ │ Δ% │ Plan │ vs Plan │ vs Plan %
- Label column first, then the primary figure.
- Comparison immediately after the value it compares to — not at the far right where the eye must travel.
- Absolute delta before percentage delta. The absolute is the more important number and readers hit it first.
- Cap at about eight columns before splitting into two tables or moving detail behind a drill-down.
- Right-align the header over a numeric column; a left-aligned header over right-aligned numbers looks broken.
Conditional formatting that earns its ink
The failure mode is a table lit up like a Christmas tree, where nothing stands out because everything does.
Rules:
- Format only the column that needs attention — usually variance, not every numeric column.
- Threshold-based, not gradient. Highlight what breaches materiality; do not shade all 200 rows by magnitude.
- Background tint, not coloured text. Coloured text at 13px fails contrast; a light wash with dark text passes.
- Never colour alone. Pair with a glyph or the sign (
artifact-accessibility). - Aim for under ~10% of cells carrying any emphasis. Above that, raise the threshold.
.variance.breach { background: var(--negative-wash); }
.variance.favourable { background: var(--positive-wash); }
In-table visuals
A small visual in a table row is often better than a separate chart.
Sparklines — 12-month trend in the width of a column.
- Must carry a reference point: a baseline, a min/max dot, or an end value. A bare line with no scale reads as "trend" but cannot be measured, and a flat line at high volatility looks identical to a flat line at low volatility.
- Consistent y-scale across rows if rows are comparable; if not, say so.
- Mark them
aria-hidden="true"and keep the value in an adjacent cell.
In-cell bars — magnitude comparison down a column.
- Bar plus the number, not bar alone.
- Shared scale down the column.
- Excellent for a ranked variance column; the eye sorts instantly.
Delta indicators — ▲ 12.4% with the glyph carrying direction and colour reinforcing.
Long tables
- Sticky header so column meaning survives scrolling:
Give it an explicit background or rows will show through.thead th { position: sticky; top: 0; background: var(--surface); z-index: 1; } - Sticky first column for wide tables, same technique with
left: 0. - Zebra striping only above ~15 rows, and very subtle. Below that it is noise.
- Row hover highlight helps track across a wide row — cheap and effective.
- Above ~500 rows, paginate or virtualize (
artifact-data-loading). - Group with subheader rows, not with alternating background blocks.
Totals
- Totals at the bottom for a statement (the reading order builds to them); at the top for a dashboard summary where the total is the headline.
- A totals row must be visually distinct — weight, a rule, and a slightly different background.
- Sticky totals on a long scrolling table are a real quality-of-life win.
- If the total does not equal the visible rows because of filtering, say so explicitly: "Total reflects all 14,882 rows; 200 shown." Otherwise the reader will try to add up the column and conclude something is broken.
Print and accessibility
@media print {
thead { display: table-header-group; } /* repeat headers on each page */
tr { break-inside: avoid; }
}
- Real
<table>markup with<th scope="col">/<th scope="row">and a<caption>. A grid of<div>s cannot be navigated by a screen reader — it cannot announce "row 4, Professional fees, $178K". - The caption states what the table is, including period and basis.
- Do not rely on colour alone for any status column.
When a table beats a chart
- Values will be read off precisely
- More than about 15 categories
- Multiple measures per row that must be compared
- The audience knows the standard layout (P&L, balance sheet, cash flow) — deviating from a familiar shape costs more than a chart gains
- Precision matters more than pattern
The strongest reporting page is usually one chart that carries the finding, and one table that carries the detail.
Related skills
data-typography— tabular figures, alignment, precisionartifact-data-loading— pagination and virtualizationartifact-accessibility— table semanticsvisual-hierarchy— where the table sitschart-annotation— captions and footnotes