Heatmap
Free · MITCalendar (GitHub-style) or matrix heatmap on one hue with cells that pop in column by column, a tooltip, arrow-key navigation that scrolls the calendar along, and a Less–More scale.
Installation
$ npx shadcn@latest add https://goodui.dev/r/heatmap.jsonThe file lands in components/goodui/heatmap.tsx and is yours to edit. With the @goodui registry added, npx shadcn@latest add @goodui/heatmap works too.
Usage
import { Heatmap } from "@/components/goodui/heatmap"
const data = bookings.map((b) => ({ date: b.day, value: b.count })) // { date: "2026-10-28", value: 14 }
export function ActivityCard() {
return <Heatmap title="Bookings per day" data={data} end="2026-11-02" weeks={26} formatValue={(v) => `${v} bookings`} />
}When to use
- Activity per day over months: bookings, orders, check-ins, commits.
- Intensity across two dimensions: weekday × hour, staff × service, region × month (matrix mode).
- Spotting patterns (busy Saturdays, dead Mondays) rather than reading exact values.
- Exact comparisons between a few values: use bar-chart.
- A single trend: use area-chart.
- Categorical identity (which team): a heatmap encodes magnitude on one hue only.
| If you need | Use |
|---|---|
| Last 6 months of daily activity | mode="calendar" data={[{ date, value }]} weeks={26} |
| Weekday × hour | mode="matrix" rows={days} columns={hours} values={grid} |
| Skewed data where a few huge days flatten the rest | scale="quantile" |
| Finer scale | steps={5} (levels above zero) |
Examples
Busiest hours matrix
<Heatmap
mode="matrix"
title="Busiest hours"
rows={["Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]}
columns={["9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20"]}
values={grid}
color="var(--chart-4)"
steps={5}
labels={{ cell: (label, value) => `${label}:00 — ${value}` }}
/>Sunday-first weeks
<Heatmap data={data} weekStart={0} locale="en-US" />API reference
| Prop | Type | Default |
|---|---|---|
| title Chart name for screen readers. | React.ReactNode | — |
| color One hue; intensity is its opacity in `steps` levels. Defaults to the theme's first chart color. | string | "var(--chart-1)" |
| mode calendar: weeks as columns, weekdays as rows (needs data). matrix: rows × columns (needs rows, columns, values). | "calendar" | "matrix" | "calendar" |
| data Calendar values by ISO date. Days without an entry count as 0. | HeatmapCell[] | — |
| end Last day of the calendar (ISO). Defaults to the latest date in data. | string | — |
| weeks Weeks shown in calendar mode. | number | 26 |
| weekStart First weekday: 1 Monday (default) or 0 Sunday. | 0 | 1 | 1 |
| rows Matrix row labels. | string[] | — |
| columns Matrix column labels. | string[] | — |
| values Matrix values, values[row][column]. | number[][] | — |
| steps Number of intensity levels above zero. | number | 4 |
| scale linear splits 0..max evenly; quantile gives each level the same number of cells. | "linear" | "quantile" | "linear" |
| cellSize Cell size in px (calendar) or minimum cell size (matrix, cells stretch to the width). | number | 12 |
| cellGap | number | 3 |
| legend Less … More scale under the chart. | boolean | true |
| format | Intl.NumberFormatOptions | — |
| locale | string | "en-US" |
| formatValue | (value: number) => string | — |
| formatDate Formats calendar dates for the tooltip and screen readers. | (date: Date) => string | — |
| labels Text for built-in controls and screen readers; pass translations here. | Partial<HeatmapLabels> | — |
Also accepts every <div> attribute.
Accessibility
| Tab | Focuses the heatmap and highlights the latest cell. |
| ArrowLeft / ArrowRight | Previous or next week (calendar) or column (matrix); the calendar scrolls to keep the cell visible. |
| ArrowUp / ArrowDown | Previous or next weekday or row. |
| Home / End | First or last cell. |
| Escape | Clears the highlight. |
- The chart is role="group" with aria-roledescription="heatmap" and a summary ("Bookings per day: 182 cells, highest 31").
- The highlighted cell is announced through a polite live region as "<date or row · column>: <value>".
- Intensity uses one hue in `steps` opacity levels on both themes; empty days use the muted surface. A Less … More scale explains it.
- The calendar scrolls horizontally inside the card (latest weeks first) instead of overflowing the page.
- Built-in strings (less, more, cell, summary, noData) go through labels; dates format through Intl in `locale` or formatDate.
Motion
- On first view cells pop in (scale 0.55 → 1 with a fade, 0.45 s, ease [0.2, 0.8, 0.2, 1]) column by column, 14 ms apart, as a pure CSS animation.
- The tooltip follows the pointed or focused cell; the active cell gets a 1.5px foreground outline.
- Reduced motion: cells are visible at once.
- Cells are plain SVG rects with a CSS entrance; hover uses one delegated pointermove handler on the SVG instead of a listener per cell. 26 weeks is 182 rects.
Notes for AI
Coding agents read these rules through MCP and llms.txt, or paste this page with Copy page.
- Calendar mode needs data (ISO dates, local time); days without an entry count as 0. Set end to pin the last day (defaults to the latest date in data).
- Matrix mode needs rows, columns and values[row][column] (same lengths).
- color is one hue (any CSS color); do not pass several colors, intensity is opacity.
- cellSize is fixed in calendar mode and a minimum in matrix mode, where cells stretch to the container width.
FAQ
How are the levels computed?
linear splits 0..max into `steps` equal bands; quantile puts the same number of non-zero cells in each level. Zero is always its own level.
Can I show a whole year?
Yes, weeks={52}; the calendar scrolls horizontally in narrow cards and starts scrolled to the latest week.
Related
Grouped, stacked or horizontal bars that grow from the baseline on view and morph when data changes, with a gliding hover band, legend toggles, value labels and 2px surface gaps between segments.
Mini Gantt whose bars grow from their start date on view, with progress fills, milestones, group headers, day / week / month ticks, a today marker and arrow-key rows.
Squarified treemap whose tinted tiles fade in largest first and spring to a new layout when values change, with labels only where they fit, a tooltip and arrow-key tiles.