HeatmapChart

A row × column grid encoding magnitude as fill intensity.

stable

Quick Preview

Listing inquiries

By neighbourhood · Jan–Jun 2026

JanFebMarAprMayJunLekkiIkoyiYabaAjah
12
96

Usage

<HeatmapChart
	title="Listing inquiries"
	description="By neighbourhood · Jan–Jun 2026"
	data={[
		{ row: "Lekki", column: "Jan", value: 42 },
		{ row: "Lekki", column: "Feb", value: 58 },
		{ row: "Yaba", column: "Jan", value: 21 },
		{ row: "Yaba", column: "Feb", value: 26 },
	]}
/>

Unlike the recharts-backed charts in this library, HeatmapChart is hand-rolled — recharts has no heatmap/matrix primitive. It is one component (ChartCard owned internally, like FunnelChart/TreeMap/BulletChart), not a set of composable wrappers: pass data as a flat array of { row, column, value } cells, and the grid, hover/focus tooltip, and sequential legend come for free.

Rows and columns default to first-seen order in data; pass explicit rows/ columns arrays to control ordering or to include categories with no data (rendered as a dashed "no data" placeholder). Wide grids (many columns) scroll horizontally inside the card rather than shrinking cells below a readable size.

Color encodes magnitude, so cells are --color-surface-brand at a magnitude-scaled opacity — 8%, 22%, 44%, 70%, 100% across the five buckets — rather than a distinct hue per step. surface-brand is already theme-aware, so the ramp stays correct in both themes with no separate token set; never the categorical chart-13 tokens, which encode identity, not amount.

Examples

Default HeatmapChart

A neighbourhood × month grid. Hover or focus a cell for the exact value; the legend under the grid anchors the color scale to the data's range.

Listing inquiries

By neighbourhood · Jan–Jun 2026

JanFebMarAprMayJunLekkiIkoyiYabaAjah
12
96

In-cell Values

Pass showValues to print the formatted value inside every cell — useful for a small, dense matrix (like a collection-rate scorecard) where the reader wants every figure at a glance, not just on hover. Pin domain so a scale tied to real thresholds (0–100%) doesn't get stretched by whatever range the current data happens to cover.

Repayment collection rate

By branch and quarter · % of scheduled repayments collected

Q1Q2Q3Q4Lekki branchIkoyi branchYaba branch
70%
100%

Customizing the Grid

Pass explicit rows/columns to fix category order and coverage, pin domain to a scale that stays comparable across datasets, and turn off legend when the card's own caption already states the scale. A wide grid (24 columns here) scrolls horizontally instead of shrinking.

Site visits

By day and hour · sessions, last 30 days

0:001:002:003:004:005:006:007:008:009:0010:0011:0012:0013:0014:0015:0016:0017:0018:0019:0020:0021:0022:0023:00MonTueWedThuFriSatSun

Props

Prop/APITypeDefaultDescription
data{ row: string; column: string; value: number }[]-One entry per row/column pair that has a value.
rowsstring[]first-seen orderRow order, top to bottom.
columnsstring[]first-seen orderColumn order, left to right.
domain[number, number]data's [min, max]Fixed bounds for the color scale.
size"sm" | "md" | "lg"inheritedCell size and label/legend scale.
titleReactNode-Chart heading. Omit to hide.
descriptionReactNode-Supporting metadata (period, source). Omit to hide.
legendbooleantrueShow the sequential-scale legend under the grid.
showValuesbooleanfalsePrint the formatted value inside every cell.
valueFormatter(value: number) => stringgrouped integerFormats the tooltip, in-cell label, and legend bounds.
classNamestring-Applied to the ChartCard.

Accessibility

  • Every valued cell is a focusable button with an aria-label carrying the row, column, and formatted value — screen readers get the exact figure without relying on the visual hover tooltip.
  • "No data" cells (a row/column pair absent from data) are non-interactive dashed placeholders, skipped from tab order — there is nothing to announce.
  • Do not communicate state with color alone; the legend and every cell's aria-label/tooltip carry the same figures the fill encodes.
  • Keep legend on (the default) whenever the color scale's bounds aren't already stated elsewhere on the page.

On this page