Skip to content

Heatmap

A value matrix — rows × columns of cells tinted by value — rendered as a navigable table.

Stablev0.4.2added in v0.2.0@garn/ui/heatmap
On 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.

terminal
npx garn-ui add heatmap
First time? Set up garn in your project
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

Do
  • Provide row/column labels + a caption; use a valueFormatter.
  • Keep the matrix modest in size so cells stay legible.
Don't
  • 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
A1.3.1Info and RelationshipsA1.1.1Non-text Content
  • 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).