Skip to content
microcharts
ReferenceFrontierConfusionGrid

ConfusionGrid

React confusion grid chart — 2×2 classification outcomes at a glance. Tiny accessible SVG microchart; RSC-safe static or /interactive.

ConfusionGrid answers "where do the errors go?" — the one thing accuracy-as-a-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 shape, never by colour, so good and bad are positions on the grid, not hues. The one-line key — rows actual, columns predicted — belongs next to every example.

ConfusionGrid
interactive · 5.2 kB · static · 2.72 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

  • Good for — classifier evaluation, and any paired-classification agreement (triage vs outcome, plan vs actual, rater A vs rater B).
  • Avoid for — a single accuracy number (Delta) or more than four classes (use a full-size heatmap).

Sizing

Variants

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 grid never silently drops classes in production (the warning is dev-only), but the render itself does truncate; pass pre-filtered labels/counts if you need a specific four.

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

Four homes

Why this default

The row view answers the question practitioners actually ask — "of the real cats, how many did we call dogs?" — so row-normalization is stated in the summary phrasing ("% of cats") and the denominator travels with every number. Accuracy is off by default and never rendered without the grid: the number may not leave its context. The diagonal accent is a shape (an inset stroke), and the ink ramp is identical for agreement and error cells — good and bad are positions, not colours, which is what keeps it readable under forced colours.

Accessibility

The accessible name states accuracy and the worst confusion, with the row-normalized denominator carried in the phrasing — 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.

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.
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