Skip to content
microcharts
ReferenceFrontierConfusionGrid

ConfusionGrid

ConfusionGrid shows where a classifier's errors land, as a grid of row-normalized shares.

encodes
row-normalized cell ink; diagonal accented by shape
precision
medium
nodes
k² + k + 2k labels

A classifier is 73% accurate; ConfusionGrid shows where the other 27% went, which is what an accuracy number hides. Rows are the actual class, columns the predicted class, and each cell's ink is the row-normalized share: of the actual X, where did the predictions go? The diagonal (agreement) is accented by an inset stroke rather than by color, and the ink ramp is the same for agreement and error cells, so good and bad are positions on the grid. That is what keeps it readable under forced colors. Put the one-line key — rows actual, columns predicted — next to every example.

ConfusionGrid
interactive · 5.35 kB · static · 2.79 kB

Install

Import & usereact/confusion-grid
import { ConfusionGrid } from "@microcharts/react/confusion-grid";// counts — real values under “Sample data” below<ConfusionGrid data={counts} title="Classifier" />
Sample data
const counts = {  labels: ["cat", "dog"],  counts: [    [88, 12],    [10, 59],  ],};
Needs package + stylesheetSet up with AI

Try it

When to use it

Use it for classifier evaluation and any paired-classification agreement: triage vs outcome, plan vs actual, rater A vs rater B. For a single accuracy number reach for Delta; for more than four classes, a full-size heatmap.

Sizing

Variants

accent="errors" moves the accent from the diagonal to the worst confusion cell. label="accuracy" prints the accuracy number; it is off by default and never rendered without the grid, so the number does not leave its context.

worst-confusion accent

Edge cases

k > 4 clamps to the first four classes (dev-warns)

A fifth class doesn't grow the grid. Legibility caps at 4×4, so a k of 5 or more clamps to the first four labels and rows/columns, with a dev warning steering to a full-size heatmap. The warning is dev-only, but the render truncates in production too; pass pre-filtered labels/counts if you need a specific four.

an all-zero row (a class with no samples)

Four homes

Accessibility

The row view answers the question practitioners ask — "of the real cats, how many did we call dogs?" — so the summary phrasing states the row normalization ("% of cats") and the denominator travels with every number. The grid at the top of this page reads "Accuracy 73%. Most confused: B predicted as C (43% of Bs)." A perfect classifier reads "Accuracy 100%. No confusion.", and a class with no samples is named rather than hidden: the cat/dog example above reads "Accuracy 80%. Most confused: cat predicted as dog (20% of cats). No dog samples." The interactive entry roves the cells with the arrow keys (Home and End jump to the two ends of the diagonal), announcing each cell's actual and predicted class as a share of the actual class, with the tally it was computed from: "Actual cat, predicted dog: 12% of cats (12)." A row percentage cannot be inverted back to a count without the row total.

The interactive entry follows the shared interaction contract: arrow keys rove between units on both axes, Home and End jump to the ends, and a click, tap, Enter or Space selects a unit — pinning its readout so it survives blur, until you select it again or press Escape. On touch, a tap pins and a drag scrubs.

Props

PropTypeNotes
data*{ labels, counts }k×k matrix; rows actual, columns predicted.
normalize"row" | "none"Row = recall view (default).
accent"diagonal" | "errors"Agreement or the worst confusion.
label"accuracy" | "none"Overall accuracy in the gutter (opt-in).
shape"square" | "round"Cell shape from the shared vocabulary (default 'square').
colorstringAccent fill override.
sizenumberGrid square edge in viewBox units. Defaults to a size that grows with the class count.
animatebooleaninteractiveOpt-in entrance motion when the chart mounts client-side — add import "@microcharts/react/motion" once. Inert on the server, on hydrated server HTML, and under prefers-reduced-motion.

Plus the shared grammar — data, domain, color, title, summary, format — and the layout props (width, height, className, style) that every chart accepts. Interactive entries also share animate and live, and — wherever a chart has more than one navigable unit — onActive, onSelect, selectedIndex and defaultSelectedIndex; and — wherever the chart shows a hover value — readout. See the shared grammar.

Related charts