Components / Charts
WaffleChart
A ten by ten unit chart where every cell is one percent, and cells fly to their new group when the data changes.
Install
npx shadcn@latest add https://irisui.dev/r/waffle-chart.jsonimport { WaffleChart } from "@/components/iris/waffle-chart/waffle-chart";Preview
WaffleChart
A ten by ten unit chart where every cell is one percent of a whole, with a legend of rolling shares and cells that fly to their new group when the data changes.
When to use
- Part-to-whole data with about four to eight categories, where countable squares read more concretely than angles: ticket outcomes, survey answers, budget splits, energy mix.
- Comparing two snapshots with the same keys (this month against last); cells move rather than repaint.
- Not for many small categories or trends over time; use
BarChartorStreamgraph. For a quick share of a few parts with a total in the centre, useDonutChart.
Usage
import { WaffleChart } from "@/components/iris/waffle-chart/waffle-chart";
h(WaffleChart, {
label: "Duplicate pairs by outcome, September 2026",
unit: "pairs",
data: [
{ key: "auto", label: "Auto merged", value: 418 },
{ key: "merged", label: "Merged after review", value: 101 },
{ key: "rejected", label: "Rejected", value: 59 },
{ key: "pending", label: "Pending review", value: 23 },
{ key: "unmerged", label: "Unmerged", value: 11 },
],
});
Props
| Prop | Type | Default | Description |
|---|---|---|---|
data |
{ key: string; label: string; value: number; color?: string }[] |
required | Categories in fill order; the first fills from the bottom-left, column by column. Shares are computed from the total; negatives count as zero. |
label |
string |
required | Names the whole for the chart, summary and table. |
unit |
string |
"" |
Unit after raw values in the tooltip and table. |
formatValue |
(value, category) => string |
— | Raw value formatter. Shares always show as percentages. |
rows / columns |
number |
10 / 10 |
Grid size; 10 × 10 is one cell per percent. |
accentKey |
string | null |
first key | Category painted in the accent. null keeps every category neutral. |
activeKey / defaultActiveKey / onActiveChange |
string | null / callback |
— / null |
Focused category; others dim. |
legend |
boolean |
true |
Category list with rolling shares. |
decimals |
number |
0 |
Decimal places for shares. |
emptyLabel |
string |
"No data" |
Shown when data is empty or sums to zero. |
className |
string |
— | Class on the root figure. |
ref |
Ref<HTMLElement> |
— | Forwarded to the root figure. |
Notes
- Keyboard: Tab focuses the grid, then each legend item. Arrow keys move the reading cell, Page Up/Down jump between categories, Home/End go to the first or last filled cell, Escape clears; Enter/Space on a legend item pins or releases it.
- Accessibility: the grid is a focusable group with instructions; cells are decorative. A polite live region announces category, share and value; a hidden summary and table list each category. Categories beyond the first few also differ in cell shape, so meaning does not rest on colour alone.
- Order categories meaningfully; fill order is reading order. Set
accentKeyto the category the story is about. - The legend sits beside the grid when the figure is at least 420px wide, and below it otherwise.
- Motion: cells dip, pop and stagger between datasets; reduced motion places them immediately.