/

Heatmaps and Contribution Graphs: A Chart Wearing a Calendar

The colour scale is the component. Sequential not rainbow, buckets not ramps, and empty is not zero.

Serafim Korablev
Serafim Korablev
@korablev

A contribution graph is the most copied chart on the internet and one of the least understood. It looks like a calendar and behaves like a chart: a grid of days coloured by intensity, which is a heatmap with a date axis. Building it as a calendar is why most implementations are hard to read and impossible to use without a mouse.

The components

GitHub calendar by Ali Imam is the contribution-graph shape. Heatmap Calendar and Monthly Heatmap Calendar by Ruixen UI are the tracking versions, Anomaly heatmap by Erik X is the operational one, and Heatmaps by visx is the low-level charting version.

Hero Heatmap by cult-ui is the decorative cousin: a WebGL shader that looks like a heatmap and carries no data, which the shader guide covers.

The colour scale is the whole design

A heatmap encodes value as colour, which makes the scale the component rather than a styling detail.

Use a sequential scale for magnitude, running from a light tint to a saturated one, and a diverging scale only when the data has a meaningful midpoint such as zero. A rainbow scale looks informative and is not: the perceived jumps between hues do not match the numeric steps, so readers see boundaries that are not in the data.

Pick the steps deliberately. Four or five buckets are easier to read than a continuous ramp, and a linear scale on skewed data produces a grid where everything is the palest bucket and one cell is dark. Quantile buckets fix that and need a legend that says so.

Keep the empty state distinct from the zero state. A day with no data and a day with zero events are different facts, and giving them the same colour is the most common way a contribution graph lies.

Working in a perceptually uniform space helps here for the same reason it helps a palette, which the gradient guide explains: equal steps in lightness look equal.

The accessibility half

Colour alone carries the entire message, which makes this the chart type with the largest gap between what it shows and what it says.

Every cell needs an accessible name with the date and the value, not just a tooltip on hover, since a tooltip does not exist on touch or for a keyboard. The grid should be a table or a list with real dates, which is also what a search engine and an answer engine read, per the chart guide and its argument that every chart owes a table equivalent.

And the palette has to survive greyscale. If two adjacent buckets are indistinguishable without colour, the scale has too many steps or too little lightness range.

The layout details

Weeks are columns, days are rows. Seven rows, one column per week, is what makes a year fit a strip.

Label the axis sparsely. Month names above the first column of each month, and one or two weekday labels, is enough. A label on every cell is a wall.

Horizontal scroll on narrow screens, inside the component's own container so the page never scrolls sideways.

Say what a cell is. "Contributions" or "Sessions" or "Errors" - the unit belongs in the legend, because the grid alone does not say what it counts, which is the same point the stats guide makes about a big number with a vague label.

Where else to look

The honest list, because the answer is not always us:

SourceBest forTrade-off
CSS grid and a bucket functionThe whole thing, honestly, in very little codeYou write the scale and the labels
visxHeatmaps as part of a real charting systemA library and a mental model
shadcn/ui ChartsThe other chart types beside itNo heatmap
21stContribution graphs, tracking calendars and operational heatmapsQuality varies by author, so preview before you take it

Taking one

Every component page has a live preview and the code. Installing goes through the shadcn CLI against our registry:

bash

That key comes from your 21st account, and installs require a membership. Set API_KEY_21ST once in your shell and the command works for anything in the catalogue.

Browse chart components →

Frequently asked

What colour scale should a heatmap use?
Sequential for magnitude, from a light tint to a saturated one, and diverging only when the data has a meaningful midpoint. Never a rainbow: the perceived jumps between hues do not match the numeric steps, so readers see boundaries that are not in the data.
Why does my contribution graph look almost entirely empty?
A linear scale on skewed data puts nearly everything in the palest bucket and one cell at the top. Quantile buckets fix it, with four or five steps rather than a continuous ramp, and a legend that says the buckets are quantiles rather than equal ranges.
How do you make a heatmap accessible?
Colour alone carries the whole message, so every cell needs an accessible name with its date and value rather than only a hover tooltip, and the grid should be a table or list with real dates, which is also what search and answer engines read. Check the palette in greyscale.
What is the difference between empty and zero on a heatmap?
A day with no data and a day with zero events are different facts, and giving them the same colour is the commonest way a contribution graph misleads. Give the no-data state its own treatment, usually a neutral outline rather than the lightest bucket.

Published

Aug 21, 2026

Read time

4 min

Tags

GuideChartsReactData

Share