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.json
import { WaffleChart } from "@/components/iris/waffle-chart/waffle-chart";
Registry item waffle-chartBrings in foundationRegistry JSON

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 BarChart or Streamgraph. For a quick share of a few parts with a total in the centre, use DonutChart.

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