Heatmap
A value matrix — rows × columns of cells tinted by value — rendered as a navigable table.
@garn/ui/heatmapOn this page
Default
Weekly retention by signup cohort (%).
Installation
garn is copy-in — the garn CLI writes this component's source into your project, so you own and can edit it. It resolves any base files and installs the npm dependencies below for you.
npx garn-ui add heatmap- Registry
heatmap- Deps
- None
- Registry deps
utils
Import
import { Heatmap } from "@garn/ui/heatmap";Props
The component's public props and their types.
captionRequired- Description
- Accessible table caption (visually hidden).
- Type
string
color- Description
- Cell tint (token reference).
- Type
string- Default
"var(--garn-chart-1)"
columnsRequired- Description
- Column labels (x axis) — one per column.
- Type
string[]
max- Description
- Value mapped to full intensity. Defaults to the largest value in `values`.
- Type
number
rowsRequired- Description
- Row labels (y axis) — one per row.
- Type
string[]
valueFormatter- Description
- Format a cell's value for the hover title + screen-reader text.
- Type
(value: number) => string- Default
(v) => v.toLocaleString()
valuesRequired- Description
- Row-major matrix of values: `values[rowIndex][colIndex]`.
- Type
number[][]
Plus 287 inherited native <table> attributes.
Styling
Target these data-slots and states, and remap these tokens, to restyle without forking the component.
[data-slot="heatmap"][data-slot="heatmap-cell"]When to use
Show a labelled value matrix — retention cohorts, correlation, hour×day activity.
Reach for something else when
- An unlabelled calendar/contribution density grid (use Activity Grid)
- a single series trend (use Sparkline/Line).
Overview
Heatmap renders a value matrix — cohort retention, correlation, hour×day activity — as an actual <table> with row and column headers, its cells tinted by value. Building it as a table (not an SVG) means it's navigable and readable to assistive tech for free, while the colour gives sighted users the at-a-glance pattern. For an unlabelled calendar-style density grid, use Activity Grid instead.
Guidelines
It's tabular data — keep it a table. Row/column headers and a caption make the matrix navigable; the colour is a second, decorative channel over an accessible base.
One hue, opacity by value. Cells tint a single --garn-chart-* token; intensity is value/peak (floored so low-but-nonzero cells stay visible). Provide a valueFormatter (e.g. %) so the hover/sr value reads right.
Constrain the width. It ships w-fit so a global full-width table style doesn't stretch the cells; keep cells squarish for a readable matrix.
Best practices
- Provide row/column labels + a
caption; use avalueFormatter. - Keep the matrix modest in size so cells stay legible.
- Don't drop the headers/caption — they're what makes it accessible.
- Don't map cells to unrelated hues; intensity is one token.
Troubleshooting
Columns spread far apart / table stretches full width.
Cause. A global table { width: 100% } overrides.
Fix. The component ships w-fit; keep it (or set an explicit width) so cells stay square.
Accessibility
- Role
table
- A real `<table>` with a `<caption>` and scoped row/column headers — navigable by assistive tech.
- Each cell exposes its value via visually-hidden text; the colour is decorative.
- Cell intensity = value / peak opacity of a `--garn-chart-*` token (floored so low values stay visible).