render-as-html · canonical primitives

Ten bones the whole system is built on.

Charts and tables that show up across the page shapes, locked to the one-truth palette, with their interaction contracts and anti-patterns written down once. Each frame below is the reference implementation — open it standalone, copy it, fork it.

10 primitives · 6 charts · 4 tables · self-contained, zero external requests · WCAG-AA · same palette as every shape


10 of 10 primitives

01 · chart

Donut — categorical status mix

A one-glance "shape of the population" summary that pairs above a dense table. The legend doubles as a cross-highlight filter — clicking a row isolates that segment and surfaces a visible × clear control.

pick when
You have 2–5 mutually-exclusive categories and the proportion is more useful at a glance than the absolute counts.
required
Subject-count in the center (never a category count); ring track in --rule-soft; legend in CSS subgrid so counts and percents align across rows.
interaction
Click a legend row to isolate that segment; visible × clear appears when a filter is active.
avoid
6+ slivers (use a stacked bar instead); donut-on-its-own as a hero — it's always paired with the data beneath it.

02 · chart

Bar — ranked top-N

Used when the question is "which N are biggest, and by how much." Bar length carries magnitude; the name lives in its own subgrid column to the left so long identifiers stay legible. Ochre marks the current leader; terracotta the rest.

pick when
Showing ≤12 items where the order matters and a single magnitude per item is the signal.
required
Name column to the left of the bar (never on the bar with text-shadow); ochre leader, terracotta rest; bars use a subgrid that aligns name / bar / value / Δ across rows.
interaction
Sort dropdown: at minimum value, alpha, and the secondary dimension (Δ, age, etc.). Bars re-scale when the sort changes.
avoid
Text-shadow / outline glow on labels; rainbow per-bar coloring; horizontal bars without a max-width cap.

03 · chart

Sparkline — stat-tile cluster

2–4 hero metrics, each with its current value, its delta vs. a prior period, and the shape of its recent trend. Tile cluster sits above the dense table; the sparkline carries direction-of-trend without becoming a separate chart.

sparkline cluster source: primitives/03-sparkline.html
pick when
The metric IS the subject (not an artifact count); current value + shape-of-recent-history is what the reader needs in one glance.
required
Single accent color for the spark line; delta carries direction-of-good (ok / accent / muted); latest-point dot is hollow (paper fill, accent stroke) so it reads as a marker, not a glitch.
interaction
Hover the spark to drop a vertical guide + value/time tip at the nearest data point.
avoid
4+ tiles (cross into dashboard territory); stat tiles that count the artifact's own production; axis tick marks for every data point (the tooltip carries precision).

04 · chart

Stacked bar — composition over time

When you need total volume and category mix across periods in one figure. Bars carry the trend; segment colors carry the mix. The legend is a chip filter that re-stacks bars when you remove a category — so dropping Success lets you see the failure rhythm.

stacked bar source: primitives/04-stacked-bar.html
pick when
Categories sum to a meaningful whole each period (success/fail buckets, traffic mix, deal stage); 7–30 periods on the x-axis.
required
Y-axis recomputes when categories toggle off; severity-ascending bottom-up stack order; per-column tooltip with the day's breakdown + total; chip legend doubles as the filter.
interaction
Hover any column for the full breakdown tooltip; click chips to add/remove categories.
avoid
Categories that don't sum cleanly (use grouped bars instead); fixed y-axis when categories can toggle off.

05 · chart

Topology — inline service graph

When connections are the point and the node count is ≤30. Hand-positioned coordinates (force-directed simulations earn their place at 30+ nodes); straight edges meet rect boundaries; click-to-focus dims everything outside the neighborhood.

pick when
Reader needs to trace connections, not scan a list; node count fits a single viewport hand-positioned (≤30).
required
Mono-uppercase node labels; paper-card node fill with a cluster-colored dot (never fill the whole node with a category color); right-rail inspector matching the editorial-shape entity pattern; edges meet rect boundaries.
interaction
Click a node to focus its neighborhood (dim others, light direct edges, update inspector); search to ochre-highlight matches independent of focus; × clear focus resets.
avoid
Force-directed wiggle on small graphs; edges drawn center-to-center poking out of nodes; an unstyled SVG with no surrounding chrome.

06 · table

Dense ops table

The instrument-register workhorse: scan results, inventories, port lists. Mono first column for identifiers, sortable headers, status pills carrying outline + dot + label (so colorblind viewers still parse via shape and text), footer rows with provenance.

dense ops table source: primitives/06-table-ops.html
pick when
The dataset is a flat list of records keyed by an identifier; ≥6 rows; filtering / sorting is the central interaction.
required
Mono first column (identifier); sans middle (labels); mono right-aligned numerics with tabular-nums; pills with outline + dot + text (no color-only); footer reports true row count (not filtered).
interaction
Click column header to sort (asc → desc → none cycle); pill-filter in toolbar with active state shown by color inversion; search across identifier + label fields.
avoid
Color-only status; footer that lies about the dataset size when filtered; sorts with no neutral state.

07 · table

Comparison matrix — items as columns

The axis flip is the shape. Items get columns; criteria get rows; a weight column on the far left tunes the aggregate live. Star marks the per-row winner; the aggregate row marks the overall. The +/− stepper is full-height so weight tuning is one click, not a fiddly spinner.

comparison matrix source: primitives/07-table-comparison.html
pick when
"X vs Y vs Z" decision matrix with shared criteria; you want the reader to tune weights and see who wins; 2–5 items, 3–10 criteria.
required
Items as columns (entities-as-rows is a dashboard, not this); weight column on the far left with a full-height +/− stepper flanking the input; aggregate row normalized to 0–100; per-criterion direction (↑ better / ↓ better) is explicit; round-trip copy as prompt with visible textarea fallback.
interaction
Step or type any weight 0–5; per-row winner + overall winner recompute on every change.
avoid
Entities-as-rows layout; tiny native spinner buttons; weights without bounds; copy buttons that imply another canonical format.

08 · table

Annotated diff — code review primitive

Findings sit in the diff, anchored to the line they touch — not in a sidebar that loses spatial context. Severity travels three ways (left bar + badge fill + strip count) so the signal survives colorblind viewing. Syntax highlighting is a tiny set of local CSS classes — no Prism, no CDN.

annotated diff source: primitives/08-diff.html
pick when
Reviewing code changes, security findings, or any line-anchored critique where adjacency to the source matters more than a side panel.
required
Severity carried three ways (strip count + left bar + badge); inline annotations with explicit L· new / L· removed labels; +/− gutter chars in their own column AND row background tint; per-finding copy as prompt with the line context embedded.
interaction
Click any line number to copy file:line reference; click finding's copy button to round-trip back as a prompt; toggle show nits to focus on critical/warning.
avoid
Findings in a sidebar separated from the lines they discuss; color-only severity; runtime tokenizers / CDN syntax highlighters.

09 · table

Log stream — chronological event table

When events arrive in real time, not when you're auditing a static set. Different from the dense ops table: rows are chronological, levels carry urgency, expand-on-click reveals the structured payload, and the table itself is a scrollable container with a live-tail indicator.

log stream source: primitives/09-logs.html
pick when
The data is a tail of timestamped events with a categorical level; newest-first reading is the default; payloads have structure worth expanding.
required
Three live-state cues (pulsing dot + LIVE/PAUSED label + button mode); ochre flash on new rows (1.2s fade); FATAL fills its pill background (other levels outline-only); counts are against full data, not filtered; payload JSON syntax-colored with the diff's token palette.
interaction
Multi-select level chips; search across messages; click any row to expand the payload; ⏸ pause tail freezes scroll position and state.
avoid
Live-only state cue (state must read when video is muted or screenshot); identical pill styling for FATAL vs ERROR; expanding a row that destroys scroll position.

10 · chart

Scatter — two-dimension trade-off

For two genuinely independent dimensions across 10+ points, where the relationship is the signal — cost vs. quality, latency vs. throughput. Median crosshair guides split the cloud into quadrants ("cheap-and-good lives top-left"); marker shape carries identity alongside color so it survives greyscale. The discipline: if the points cluster on one axis, a scatter lies — rank with a bar instead.

pick when
Two independent, well-spread dimensions across ≥10 points; the trade-off / outliers / quadrants are the signal, not a ranking.
required
Both axes labeled with units + direction-of-good; log scale when a dimension spans >2 orders of magnitude; median crosshair guides with labeled medians; marker identity carried by shape AND color; hover/focus reveals name + both coords with a <title> for assistive tech.
interaction
Toggle always-on labels (off when dense — hover carries identity); toggle median guides; hover or keyboard-focus a point to pin a readout.
avoid
Scatter for ranking (that's a bar); always-on labels on a dense cloud (they collide — the omega-3 failure); an unscaled axis crushing a long-tailed dimension into the corner.

All ten primitives share the one-truth palette and the shape-chosen register split — several sit naturally inside instrument-register shapes (dashboard, comparison, developer, execution-log, runbook, triage-board, network-map), while others compose into reading or hybrid shapes when prose, slides, or transcripts need a real chart or table. Same tokens, same content-discipline rules.

No primitive matches that. to see all ten.