2 Heatmap
L4stIdi0t edited this page 2026-10-09 22:01:06 +02:00

Heatmap

A matrix of magnitudes — hour against weekday, endpoint against day, anything with two labelled axes and one number in each cell.

Colour is the only channel a heatmap has, so it uses a sequential ramp: one hue mixed toward the muted surface, light for little and saturated for a lot. Never a rainbow, which has no order in it. The cells fade in in a diagonal wave as the grid mounts. Hover or arrow onto one for its exact value; the grid is one tab stop and the arrows move in two dimensions. Every cell is also a row in a visually-hidden table, which is what makes the data readable when the colour is not. The table sits inside a clipped box: a table grows to fit its content whatever width it is given, so on its own it would widen a phone page.

Charts · keyboard contract

<script lang="ts">
	import { Heatmap } from '@l4st/driftwood';
</script>
Light Dark
heatmap heatmap in dark mode

Props

Prop Type Default What it does
rows required string[] — Row labels, top to bottom.
columns required string[] — Column labels, left to right.
values required (number | null)[][] — values[row][column]. null is "no data" and renders as an empty cell.
color string 'var(--chart-1)' The hue the ramp is built from. One hue, always — mixed toward --muted.
max number — Force the top of the ramp. Defaults to the largest value present.
format (value: number) => string compact Formats the tooltip, the scale and the table.
cellHeight number 24 Cell height in pixels; the width follows the container.
labelEvery number — Show every nth column label. Defaults to as many as fit.
rowsLabel string 'Row' Heads the row-label column of the screen-reader table — what the rows are ('Day', 'Volume').
aria-label required string — Names the grid, and captions the table. Required.
class string — Merged with the component's own classes, last — so yours win.

Keyboard

Key Does
← / → move along a row
↑ / ↓ move between rows
Home / End first / last cell in the row
Escape clear the highlight

Example

The markup behind the screenshot above, lifted from src/routes/shots/ — so it compiles, and it is exactly what produced the image.

<Heatmap
	rows={['volume1', 'volume2', 'volume3', 'volume4']}
	columns={['00', '03', '06', '09', '12', '15', '18', '21']}
	values={[
		[2, 1, 0, 8, 14, 19, 22, 9],
		[3, 1, 1, 9, 17, 24, 20, 7],
		[1, 0, 2, 11, 21, 26, 18, 6],
		[4, 2, 1, 7, 12, 16, 25, 12]
	]}
	rowsLabel="Volume"
	aria-label="Reads per second, by volume and hour"
/>

Notes

The ramp

Colour is the only channel a heatmap has, so it uses a sequential ramp: the one hue in color, mixed toward --muted in proportion to the value. One hue, light to dark — never a rainbow, which has no order a reader can rank, and never a diverging pair unless the data actually has a meaningful midpoint.

The ramp starts at an 8% mix rather than 0, so a real but tiny value never renders identically to an empty cell. null is the empty cell.

What it is for

The shape of a pattern — when the load arrives, which row is unlike the others. It is bad at exact values, which is what the tooltip and the visually-hidden table are for. If the reader needs to compare precise numbers, they want a BarChart.

Charts · LineChart · BarChart · DonutChart · ShareBar · Gauge · Sparkline · ChartLegend


← All components · Getting started · Theming · source