GitHub Heatmap

GitHub-style contribution heatmap calendar with customizable colors, tooltips, and legend

Installation

pnpm dlx shadcn@latest add "https://vritti.thesatyajit.com/r/github-heatmap"

Examples

Compact

"use client";

import HeatmapCalendar from "@/components/vritti/github-heatmap";
import type { HeatmapDatum } from "@/components/vritti/github-heatmap";

function generateSampleData(): HeatmapDatum[] {
  const data: HeatmapDatum[] = [];
  const end = new Date();
  for (let i = 0; i < 180; i++) {
    const date = new Date(end);
    date.setDate(date.getDate() - i);
    const value = Math.random() > 0.4 ? Math.floor(Math.random() * 12) : 0;
    data.push({ date, value });
  }
  return data;
}

const sampleData = generateSampleData();

export function HeatmapCalendarCompactExample() {
  return (
    <div className="w-full overflow-x-auto p-4">
      <HeatmapCalendar
        title="Last 6 Months"
        data={sampleData}
        rangeDays={180}
        cellSize={10}
        cellGap={2}
        axisLabels={false}
        legend={{ placement: "bottom" }}
      />
    </div>
  );
}

Custom Colors

"use client";

import HeatmapCalendar from "@/components/vritti/github-heatmap";
import type { HeatmapDatum } from "@/components/vritti/github-heatmap";

function generateSampleData(): HeatmapDatum[] {
  const data: HeatmapDatum[] = [];
  const end = new Date();
  for (let i = 0; i < 365; i++) {
    const date = new Date(end);
    date.setDate(date.getDate() - i);
    const rand = Math.random();
    let value = 0;
    if (rand > 0.3) value = Math.floor(Math.random() * 3);
    if (rand > 0.7) value = Math.floor(Math.random() * 8) + 3;
    if (rand > 0.9) value = Math.floor(Math.random() * 10) + 8;
    data.push({ date, value });
  }
  return data;
}

const sampleData = generateSampleData();

export function HeatmapCalendarCustomColorsExample() {
  return (
    <div className="w-full overflow-x-auto p-4">
      <HeatmapCalendar
        title="Custom Green Palette"
        data={sampleData}
        palette={["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"]}
      />
    </div>
  );
}

Interactive

"use client";

import { useState } from "react";
import HeatmapCalendar from "@/components/vritti/github-heatmap";
import type { HeatmapDatum, HeatmapCell } from "@/components/vritti/github-heatmap";

function generateSampleData(): HeatmapDatum[] {
  const data: HeatmapDatum[] = [];
  const end = new Date();
  for (let i = 0; i < 365; i++) {
    const date = new Date(end);
    date.setDate(date.getDate() - i);
    const rand = Math.random();
    let value = 0;
    if (rand > 0.3) value = Math.floor(Math.random() * 3);
    if (rand > 0.7) value = Math.floor(Math.random() * 8) + 3;
    if (rand > 0.9) value = Math.floor(Math.random() * 10) + 8;
    data.push({ date, value });
  }
  return data;
}

const sampleData = generateSampleData();

export function HeatmapCalendarInteractiveExample() {
  const [selected, setSelected] = useState<HeatmapCell | null>(null);

  return (
    <div className="w-full space-y-4 overflow-x-auto p-4">
      <HeatmapCalendar
        title="Click a cell"
        data={sampleData}
        onCellClick={setSelected}
        palette={["#f0f0f0", "#c4edde", "#7ac7c4", "#f73859", "#384259"]}
      />
      {selected && (
        <div className="rounded-md border bg-muted/50 p-3 text-sm">
          <span className="font-medium">{selected.label}</span> &mdash;{" "}
          {selected.value} {selected.value === 1 ? "event" : "events"}
        </div>
      )}
    </div>
  );
}