# ConfusionGrid (/docs/charts/confusion-grid)

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.

```tsx
import { ConfusionGrid } from "@microcharts/react/confusion-grid";

const counts = { labels: ["A", "B", "C"], counts: [ [70, 8, 2], [6, 62, 12], [3, 9, 58], ], };

<ConfusionGrid data={counts} label="accuracy" title="Classifier" />
```

## Install

```tsx
import { ConfusionGrid } from "@microcharts/react/confusion-grid";

<ConfusionGrid data={counts} title="Classifier" />
```

Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.

## Try it

```tsx
import { ConfusionGrid } from "@microcharts/react/confusion-grid/interactive";

<ConfusionGrid
  data={{ labels, counts }}
/>
```

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

**KPI card**

```tsx
<ConfusionGrid data={counts} label="accuracy" size={64} />
```

**worst-confusion accent**

```tsx
<ConfusionGrid data={counts} accent="errors" />
```

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

```tsx
<ConfusionGrid data={cm} accent="errors" />
```

## Edge cases

```tsx
 4 clamps to the first four classes (dev-warns)"
  code={`<ConfusionGrid
  data={{
    labels: ["A", "B", "C", "D", "E"],
    counts: [
      [40, 3, 1, 0, 2],
      [4, 38, 2, 1, 0],
      [1, 3, 44, 2, 1],
      [0, 2, 3, 36, 4],
      [2, 1, 0, 3, 41],
    ],
  }}
  title="5 classes"
/>`}
  expectWarn
>
  <ConfusionGrid
    data={{
      labels: ["A", "B", "C", "D", "E"],
      counts: [
        [40, 3, 1, 0, 2],
        [4, 38, 2, 1, 0],
        [1, 3, 44, 2, 1],
        [0, 2, 3, 36, 4],
        [2, 1, 0, 3, 41],
      ],
    }}
    title="5 classes"
    size={110}
  />
```

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.

```tsx
<ConfusionGrid
  data={{
    labels: ["cat", "dog"],
    counts: [
      [40, 10],
      [0, 0],
    ],
  }}
  title="No dog samples"
/>
```

## Four homes

**In a sentence**

```tsx
<p>
  Classifier accuracy{" "}
  <span className="mc-inline">
    <ConfusionGrid data={{ labels, counts }} summary={false} />
  </span>{" "}
  — 87% cats correct, 12% dogs misclassified.
</p>
```

**In a table cell**

```tsx
<td>
  <ConfusionGrid data={{ labels, counts }} />
</td>
```

**In a KPI card**

```tsx
<div className="kpi">
  <span className="figure">87%</span>
  <ConfusionGrid data={{ labels, counts }} />
</div>
```

**In a tab header**

```tsx
<button className="tab">
  cats <ConfusionGrid data={{ labels, counts }} />
</button>
```

Best at KPI/card scale — the matrix is hard to read below ~36px.

## 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](/docs/accessibility#one-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, press `Escape`, or
press outside the chart. On touch, a tap pins and a drag scrubs.

## Props

| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ 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'). |
| `color` | `string` | Accent fill override. |
| `size` | `number` | Grid square edge in viewBox units. Defaults to a size that grows with the class count. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
