Charts
Heatmap
A two-dimensional category matrix whose cells encode a numeric magnitude.
When to use it: Use it to scan patterns across two categorical dimensions.
Example
import {
ChartDescription,
ChartTable,
ChartTitle,
ChartTooltip,
Heatmap,
HeatmapCell,
HeatmapPlot,
} from "@comp0/react";
const weeks = ["W1", "W2", "W3", "W4", "W5", "W6", "W7", "W8", "W9", "W10"] as const;
const weekdays = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] as const;
const contributionCounts = [
[0, 1, 3, 2, 0, 0, 0],
[1, 4, 6, 3, 2, 0, 0],
[0, 2, 5, 7, 4, 1, 0],
[2, 6, 9, 5, 3, 0, 1],
[1, 3, 8, 11, 6, 2, 0],
[4, 8, 12, 9, 7, 1, 0],
[3, 7, 10, 13, 8, 2, 1],
[1, 5, 8, 6, 4, 0, 0],
[0, 3, 5, 4, 2, 1, 0],
[2, 4, 7, 5, 3, 0, 0],
] as const;
const contributions = weeks.flatMap((week, weekIndex) =>
weekdays.map((weekday, weekdayIndex) => ({
x: week,
y: weekday,
value: contributionCounts[weekIndex]![weekdayIndex]!,
})),
);
const formatContributions = (value: number) =>
value === 1 ? "1 contribution" : `${value} contributions`;
function contributionColor(value: number) {
if (value >= 10) {
return "fill-emerald-800 bg-emerald-800 dark:fill-emerald-300 dark:bg-emerald-300";
}
if (value >= 7) {
return "fill-emerald-600 bg-emerald-600 dark:fill-emerald-500 dark:bg-emerald-500";
}
if (value >= 4) {
return "fill-emerald-400 bg-emerald-400 dark:fill-emerald-700 dark:bg-emerald-700";
}
if (value >= 1) {
return "fill-emerald-200 bg-emerald-200 dark:fill-emerald-900 dark:bg-emerald-900";
}
return "fill-zinc-100 bg-zinc-100 dark:fill-zinc-800 dark:bg-zinc-800";
}
export function Example() {
return (
<Heatmap
values={contributions}
xLabel="Week"
yLabel="Weekday"
valueLabel="Contributions"
formatValue={formatContributions}
className="w-full max-w-2xl rounded-lg has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-offset-4 has-[:focus-visible]:outline-emerald-600 dark:has-[:focus-visible]:outline-emerald-400"
>
<ChartTitle className="text-base font-semibold text-zinc-950 dark:text-zinc-50">
Contribution activity
</ChartTitle>
<HeatmapPlot
aria-label="Contribution heatmap by weekday and week"
className="mx-auto mt-5 aspect-square w-full max-w-lg overflow-visible"
>
{(cell) => (
<HeatmapCell
key={`${cell.value.x}-${cell.value.y}`}
cell={cell}
className="group outline-none"
>
<rect
x={cell.x + 1}
y={cell.y + 1}
width={Math.max(0, cell.width - 2)}
height={Math.max(0, cell.height - 2)}
rx="1.5"
className={`${contributionColor(cell.value.value)} stroke-transparent group-data-active:stroke-zinc-950 dark:group-data-active:stroke-white`}
strokeWidth="2"
vectorEffect="non-scaling-stroke"
/>
</HeatmapCell>
)}
</HeatmapPlot>
<div
aria-label="Contribution intensity"
className="mt-3 flex items-center justify-end gap-1 text-xs text-zinc-600 dark:text-zinc-400"
>
<span>Less</span>
{[0, 2, 5, 8, 12].map((value) => (
<span
key={value}
aria-hidden="true"
className={`size-3 rounded-[2px] ${contributionColor(value)}`}
/>
))}
<span>More</span>
</div>
<ChartDescription className="mt-5 text-sm text-zinc-600 dark:text-zinc-400">
Activity peaks in weeks 6 and 7, with most contributions landing on weekdays.
</ChartDescription>
<div className="mt-4 overflow-x-auto">
<ChartTable className="w-full min-w-[36rem] border-collapse text-left text-sm [&_td]:border-t [&_td]:border-zinc-200 [&_td]:py-2 [&_th]:border-zinc-200 [&_th]:py-2 dark:[&_td]:border-zinc-800 dark:[&_th]:border-zinc-800">
<caption className="sr-only">Contributions by weekday and week</caption>
<thead>
<tr>
<th scope="col">Weekday</th>
{weeks.map((week) => (
<th key={week} scope="col">
{week}
</th>
))}
</tr>
</thead>
<tbody>
{weekdays.map((weekday, weekdayIndex) => (
<tr key={weekday}>
<th scope="row">{weekday}</th>
{weeks.map((week, weekIndex) => (
<td key={week}>{contributionCounts[weekIndex]![weekdayIndex]}</td>
))}
</tr>
))}
</tbody>
</ChartTable>
</div>
<ChartTooltip className="pointer-events-none z-50 rounded-md bg-zinc-950 px-2 py-1 text-sm text-white shadow-lg dark:bg-zinc-50 dark:text-zinc-950" />
</Heatmap>
);
}Anatomy
Dashed frames are invisible state providers; shaded shapes own real DOM. Numbered pins match the list below.
A wireframe sketch of the assembled component. Each numbered marker matches a part in the list that follows.
Heatmap
Native figure sharing categorical coordinates, values, labels, and formatting. Owns a DOM element.
ChartTitle
Native figcaption that visibly names the figure. Owns a DOM element.
HeatmapPlot / HeatmapCell
SVG matrix with one keyboard-reachable rectangle per supplied coordinate. Owns a DOM element.
ChartDescription
Visible prose summarizing the strongest pattern. Owns a DOM element.
ChartTable
Native matrix table with explicit row and column headers. Owns a DOM element.
ChartTooltipOptional
Optional floating cell value shown on hover or focus. Owns a DOM element.
Step by step
- 1
Add the main part
Start Heatmap with unique x and y coordinate pairs and labels for both axes and the measured value.
- 2
Add the supporting parts
Wrap every rendered rectangle in HeatmapCell so arrow keys follow rows and columns.
- 3
Make the behavior clear
Use a labelled color scale, a native matrix table, and an optional ChartTooltip for exact values.
Exampletsx <Heatmap values={contributions} xLabel="Week" yLabel="Weekday" valueLabel="Contributions"> <ChartTitle>Contribution activity</ChartTitle> <HeatmapPlot aria-label="Contribution heatmap by weekday and week"> {(cell) => ( <HeatmapCell cell={cell}> <rect x={cell.x} y={cell.y} width={cell.width} height={cell.height} /> </HeatmapCell> )} </HeatmapPlot> <ChartTable> <caption>Contributions by weekday and week</caption> </ChartTable> <ChartTooltip /> </Heatmap>;
Keyboard
- ⇥
- Enters the chart at its current cell and leaves with one more Tab.
- ←→↑↓
- Moves one available cell in the requested row or column direction.
- HomeEnd
- Moves to the first or last supplied cell.
- Esc
- Dismisses an open ChartTooltip.
Forms and accessibility
Charts are descriptive content and do not create form values.
Accessibility checklist
- Keep both category axes visible and explain what the color intensity measures.
- Wrap every custom rectangle in HeatmapCell for row-and-column arrow navigation.
- Use a sequential palette with distinguishable contrast and never rely on color without numeric labels or a table.
- Include a native table with real row and column headers.
- ChartTooltip may reveal exact values visually but must not replace the table.
API reference
import { ChartDescription, ChartTable, ChartTitle, ChartTooltip, Heatmap, HeatmapCell, HeatmapPlot } from "@comp0/react";Heatmap
DOM elementNative figure sharing categorical coordinates, values, labels, and formatting.
| Prop | Type | Description |
|---|---|---|
values | readonly HeatmapChartValue[] | Unique x and y label pairs with finite values. |
xLabel | string | Visible headings for both dimensions and the encoded measure. |
yLabel | string | Visible headings for both dimensions and the encoded measure. |
valueLabel | string | Visible headings for both dimensions and the encoded measure. |
formatValue | (value: number) => string | Formats cell names and table cells. |
ChartTitle
DOM elementNative figcaption that visibly names the figure.
HeatmapPlot / HeatmapCell
DOM elementSVG matrix with one keyboard-reachable rectangle per supplied coordinate.
| Prop | Type | Description |
|---|---|---|
aria-label | string | Concise text alternative naming the matrix and measure. |
children | (cell: HeatmapCellState) => ReactNode | Custom cell renderer receiving its coordinate, value, and geometry. |
cell | HeatmapCellState | Cell state passed from the plot to HeatmapCell. |
ChartDescription
DOM elementVisible prose summarizing the strongest pattern.
ChartTable
DOM elementNative matrix table with explicit row and column headers.
ChartTooltip
OptionalDOM elementOptional floating cell value shown on hover or focus.
Style hooks
Attributes that appear while a state is true. Target them with Tailwind data variants such as data-open:bg-zinc-100, or with any CSS selector.
HeatmapPlot / HeatmapCell
| Style hook | Meaning |
|---|---|
[data-value] | The numeric value encoded by this cell. |
[data-active] | The cell currently reached by pointer or keyboard. |
ChartTooltip
| Style hook | Meaning |
|---|---|
[data-open] | A value tooltip is visible. |