Heatmap Chart

A GitHub-style contribution heatmap with animated cells, configurable level colors and patterns, and an interactive legend

Less
More

Installation

pnpm dlx shadcn@latest add @bklit/heatmap-chart

When using loadingLabel or HeatmapChartLoading, centered shimmer text uses @bklit/shimmering-text — installed automatically with @bklit/heatmap-chart.

Usage

The Heatmap Chart uses a composable API. Wrap the chart and legend in HeatmapInteractionProvider so hover dimming and tooltips stay in sync:

import {
  HeatmapCells,
  HeatmapChart,
  HeatmapInteractionBoundary,
  HeatmapInteractionProvider,
  HeatmapLegend,
  HeatmapTooltip,
  HeatmapXAxis,
  HeatmapYAxis,
} from "@bklitui/ui/charts";

const data = [
  {
    bin: 0,
    bins: [
      { bin: 0, count: 2, date: new Date(2024, 0, 1) },
      { bin: 1, count: 0, date: new Date(2024, 0, 2) },
      // …one week column with 7 day bins
    ],
  },
  // …one column per week
];

export default function ContributionHeatmap() {
  return (
    <HeatmapInteractionProvider>
      <HeatmapInteractionBoundary>
        <HeatmapChart data={data} layout="fluid">
          <HeatmapCells />
          <HeatmapXAxis />
          <HeatmapYAxis />
          <HeatmapTooltip />
        </HeatmapChart>
        <HeatmapLegend />
      </HeatmapInteractionBoundary>
    </HeatmapInteractionProvider>
  );
}

Components

HeatmapChart

The root component that sizes the grid, builds color scales, and provides context to children.

PropTypeDefaultDescription
dataHeatmapColumn[]requiredOne column per week (or category) with row bins inside
xDomain[Date, Date]-Visible time range — filters week columns
sizingColumnCountnumber-Column count for stable cell sizing when scrubbing
layout"fluid" | "fill""fluid"fluid hugs content height; fill expands cells to parent
marginPartial<Margin>{ top: 28, right: 16, bottom: 0, left: 40 }Chart margins
binSizenumber0Fixed cell size in px; 0 sizes cells to fit
gapnumber2Gap between cells in pixels
colorScale(count) => string-Override the default color scale
levelColorsHeatmapLevelColorsGitHub greensFive colors for empty + four activity levels
levelStylesHeatmapLevelStyles-Per-level color and optional pattern; takes precedence over levelColors
aspectRatiostring-CSS aspect ratio for the outer container
statusChartStatus"ready"Fetch / display status
loadingLabelstring-Centered label while loading
animationDurationnumber1600Enter animation duration in ms
animatebooleantruePlay enter fade-in / loading shimmer
classNamestring""Additional CSS class

HeatmapCells

Renders the grid of cells with enter animation and hover dimming.

PropTypeDefaultDescription
cornerRadiusnumber2Corner radius for each cell
colorScale(count) => string-Override chart color scale
fadedOpacitynumber0.3Opacity for non-hovered cells while hovering
interactivebooleantruePointer hover and dimming

HeatmapXAxis / HeatmapYAxis

Month labels along the top and weekday labels along the left.

HeatmapLegend

Less → More scale swatches that share levelStyles with the chart.

PropTypeDefaultDescription
lessLabelstring"Less"Label before swatches
moreLabelstring"More"Label after swatches
cellSizenumber11Swatch size in pixels
gapnumber2Gap between swatches
cornerRadiusnumber2Swatch corner radius
align"start" | "center" | "end""end"Horizontal alignment
levelStylesHeatmapLevelStyles-Shared level colors and patterns
fadedOpacitynumber0.3Opacity for non-highlighted swatches while interacting
interactivebooleanautoSync dimming with chart hover

HeatmapTooltip

Shows the contribution count and date for the hovered cell.

PropTypeDefaultDescription
formatLabel(count, date) => stringdefault formatterCustom label for the hovered cell
panelStyleCSSProperties-Inline styles for the tooltip panel
classNamestring""Additional CSS class

Level styles and patterns

Pass levelStyles (five entries: empty + levels 1–4) to control both cell fills and legend swatches. Each level can be solid or use a pattern preset:

const levelStyles = [
  { color: "var(--color-muted)", fillMode: "solid" },
  { color: "#0e4429", fillMode: "solid" },
  { color: "#006d32", fillMode: "pattern", pattern: "diagonal", patternColor: "#39d353" },
  { color: "#26a641", fillMode: "solid" },
  { color: "#39d353", fillMode: "solid" },
] as const;

<HeatmapChart data={data} levelStyles={levelStyles}>
  <HeatmapCells />
</HeatmapChart>
<HeatmapLegend levelStyles={levelStyles} />

Data format

interface HeatmapBin {
  bin: number;       // row index (0–6 for days of week)
  count: number;     // activity level 0–4
  date: Date;
}

interface HeatmapColumn {
  bin: number;       // column index (week number)
  bins: HeatmapBin[];
}

Counts map to five visual levels (0 = empty, 1–4 = increasing activity). Use getHeatmapContributionLevel(count) from @bklitui/ui/charts to derive the level from a raw count.

See the charts gallery for pattern fills and layout variants.