Charts
Heatmap Chart
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,
HeatmapChart,
HeatmapChartCell,
HeatmapChartPlot,
} 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 (
<HeatmapChart
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>
<HeatmapChartPlot
aria-label="Contribution heatmap by weekday and week"
className="mx-auto mt-5 aspect-square w-full max-w-lg overflow-visible"
>
{(cell) => (
<HeatmapChartCell
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"
/>
</HeatmapChartCell>
)}
</HeatmapChartPlot>
<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]:sr-only" />
</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" />
</HeatmapChart>
);
}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.
HeatmapChart
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.
HeatmapChartPlot / HeatmapChartCell
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 table that renders a complete default from the chart data: a caption from the ChartTitle text or axis labels, column headers, and one row per value. Pass children for a custom body. 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 HeatmapChart 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 HeatmapChartCell 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 <HeatmapChart values={contributions} xLabel="Week" yLabel="Weekday" valueLabel="Contributions"> <ChartTitle>Contribution activity</ChartTitle> <HeatmapChartPlot aria-label="Contribution heatmap by weekday and week"> {(cell) => ( <HeatmapChartCell cell={cell}> <rect x={cell.x} y={cell.y} width={cell.width} height={cell.height} /> </HeatmapChartCell> )} </HeatmapChartPlot> <ChartTable /> <ChartTooltip /> </HeatmapChart>;
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 HeatmapChartCell 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, HeatmapChart, HeatmapChartCell, HeatmapChartPlot } from "@comp0/react";HeatmapChart
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.
HeatmapChartPlot / HeatmapChartCell
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: HeatmapChartCellState) => ReactNode | Custom cell renderer receiving its coordinate, value, and geometry. |
cell | HeatmapChartCellState | Cell state passed from the plot to HeatmapChartCell. |
ChartDescription
DOM elementVisible prose summarizing the strongest pattern.
ChartTable
DOM elementNative table that renders a complete default from the chart data: a caption from the ChartTitle text or axis labels, column headers, and one row per value. Pass children for a custom body.
ChartTooltip
OptionalDOM elementOptional floating cell value shown on hover or focus.
| Prop | Type | Description |
|---|---|---|
placement | PopoverPlacement | Side of the active mark; defaults to "top". |
offset | number | Distance from the active mark; defaults to eight pixels. |
children | ReactNode | (details: ChartValueDetails) => ReactNode | Custom content receiving the active cell's formatted details. |
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.
HeatmapChartPlot / HeatmapChartCell
| Style hook | Meaning |
|---|---|
[data-x] | The column label of this cell. |
[data-y] | The row label of this cell. |
[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. |