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

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.

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


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


## Variants

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

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


## 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](/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 or press `Escape`.
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. |
| `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).
