@danielwh2/contribution-graph
v1.0.18
Published
Framework-agnostic GitHub-style contribution heatmap for any app.
Maintainers
Readme
contribution-graph
Framework-agnostic GitHub-style contribution heatmap for any app.
Render a year of activity into any element as a crisp SVG or HTML grid — week columns, day rows, month labels, weekday labels, intensity colors, smooth slot-text tooltips, custom cell colors, and click handlers.
📦 Install
npm install @danielwh2/contribution-graphpnpm add @danielwh2/contribution-graphbun add @danielwh2/contribution-graphyarn add @danielwh2/contribution-graph🚀 Quick start
import "@danielwh2/contribution-graph/style.css";
import { contributionGraph } from "@danielwh2/contribution-graph";
const data = [
{ date: "2026-06-01", count: 3 },
{ date: "2026-06-02", count: 7 },
// ...one entry per day with contributions
];
const graph = contributionGraph(document.querySelector("#graph")!, data, {
until: "2026-06-22",
onCellClick: (day) => console.log(day.date, day.count),
});
graph.set(newData); // update
graph.destroy(); // clean upImport the CSS once, pass your data, get a heatmap. That's it.
🧩 API
interface ContributionDay {
date: string; // YYYY-MM-DD
count: number;
color?: string; // optional per-cell color override
}
interface ContributionGraphController {
readonly element: HTMLElement;
set(data: ContributionDay[], options?: ContributionGraphOptions): void;
destroy(): void;
}
function contributionGraph(
element: HTMLElement,
data: ContributionDay[],
options?: ContributionGraphOptions,
): ContributionGraphController;| Method | What it does |
| --- | --- |
| set(data, options?) | Re-render with new data and/or options |
| destroy() | Remove the graph and clean up listeners |
⚛️ React
import "@danielwh2/contribution-graph/style.css";
import { ContributionGraph } from "@danielwh2/contribution-graph/react";
<ContributionGraph
data={data}
options={{ until: "2026-06-22", onCellClick: (day) => alert(day.date) }}
/>💚 Vue
<script setup lang="ts">
import "@danielwh2/contribution-graph/style.css";
import { ContributionGraph } from "@danielwh2/contribution-graph/vue";
</script>
<template>
<ContributionGraph :data="data" :options="{ until: '2026-06-22' }" />
</template>🔷 Solid
import "@danielwh2/contribution-graph/style.css";
import { contributionGraph } from "@danielwh2/contribution-graph/solid";
<div use:contributionGraph={{ data, options: { until: "2026-06-22" } }} />🧡 Svelte
<script lang="ts">
import "@danielwh2/contribution-graph/style.css";
import { contributionGraph } from "@danielwh2/contribution-graph/svelte";
</script>
<div use:contributionGraph={{ data, options: { until: "2026-06-22" } }} />⚙️ Options
| Option | Default | Description |
| --- | --- | --- |
| weeks | 53 | Number of week columns |
| cellSize | 11 | Cell size in px |
| cellGap | 3 | Gap between cells in px |
| cellRadius | 3 | Cell corner radius in px |
| colors | GitHub greens | Colors from least to most active (length = levels) |
| levels | 5 | Intensity levels including the empty level |
| showLabels | true | Show month and weekday labels |
| labelColor | #8b949e | Label text color |
| labelFontSize | 11 | Label font size in px |
| until | today (UTC) | End date of the range, Date or YYYY-MM-DD |
| tooltip | — | (day) => string \| null for smooth popup tooltips |
| onCellClick | — | (day) => void — day has date, count, level |
| showCounts | false | Use HTML cells. Pair with countFontSize to show in-cell counts |
| countColors | auto-contrasted | Text colors per level for in-cell count labels |
| countFontSize | 0 | Count label font size in px. 0 hides in-cell counts |
| monthLabels | ["Jan", "Feb", ...] | Array of 12 month labels. Use "" to hide a month |
| weekdayLabels | ["Mon", "", "Wed", ...] | Array of 7 weekday labels (Mon..Sun). Use "" to hide a row |
| weekStart | 1 | First day of each column: 0 = Sunday, 1 = Monday |
| hud | false | Wrap the graph in the cinematic "GIT COMMITS" HUD (see below) |
| profile | false | Wrap the graph in a sleek luxury GitHub profile widget (see below) |
💎 Profile mode (Luxury)
Set profile: { username: "yourname" } to wrap the component in a stunning dark-mode GitHub profile card. It mimics the classic GitHub UI but elevates it with a deep #0d1117 background, floating drop shadows, a smooth glowing effect on the activity cells, and a bold activity count.
import "@danielwh2/contribution-graph/style.css";
contributionGraph(el, data, {
profile: { username: "zzzzshawn" },
until: "2026-06-22"
});🛰️ HUD mode
Set hud: true to render the graph inside a cinematic heads-up display: a
corner-bracketed glass panel over an animated teal→ember glitch backdrop, with
auto-computed week stats and commit streak rolling in via
slot-text.
import "@danielwh2/contribution-graph/style.css";
import "slot-text/style.css"; // required — powers the rolling numbers
contributionGraph(el, data, { hud: true, until: "2026-06-22" });The HUD derives everything it shows straight from your data — this week and
last week totals, the delta %, and the current day streak (with delta
coloring: green when up, amber when down). It applies its own monochrome,
Sunday-first, single-letter-weekday defaults; any explicit option above still
overrides them (e.g. pass colors for a different ramp, or cellSize). The
whole HUD is scoped inside the host element, so size and place it like any
other block.
🎨 Custom colors
contributionGraph(el, data, {
colors: ["#ebedf0", "#c6e48b", "#7bc96f", "#239a3b", "#196127"],
levels: 5,
});Any palette works — pass 5 colors for 5 levels, 4 for 4, etc. The first color is always the "no contributions" color.
To override the color of individual specific cells, pass a color string in your data:
const data = [
{ date: "2026-06-21", count: 12 },
{ date: "2026-06-22", count: 50, color: "#ff0000" }, // specific override
];GitHub contribution data
GitHub's contribution calendar is available through the official GraphQL API. Use the exported helper to fetch and normalize it into ContributionDay[]:
import { contributionGraph, fetchGitHubContributions } from "@danielwh2/contribution-graph";
const result = await fetchGitHubContributions({
username: "octocat",
token: process.env.GITHUB_TOKEN,
from: "2026-01-01",
to: "2026-12-31",
});
contributionGraph(el, result.data, {
until: result.to,
});For browser apps, do not ship a GitHub token to users. Call fetchGitHubContributions() from your server with token, or call a safe server proxy from the browser with endpoint and no token. The helper only sends token to GitHub's official GraphQL endpoint:
const result = await fetchGitHubContributions({
username: "octocat",
endpoint: "/api/github-contributions",
});If you want to preserve GitHub's per-day colors instead of using your palette, set includeGitHubColors: true.
💬 Tooltips
contributionGraph(el, data, {
tooltip: (day) =>
day.count === 0
? `No contributions on ${day.dateLabel}`
: `${day.count} contributions on ${day.dateLabel}`,
});Each day carries date (raw YYYY-MM-DD), count, level, and
dateLabel — a human-readable UTC date produced with
dayjs (e.g. Jun 22, 2026). Return null to disable
the tooltip for a given day.
🖱️ Click handling
contributionGraph(el, data, {
onCellClick: (day) => {
console.log(day.date, day.dateLabel, day.count, day.level);
},
});🔢 HTML mode and optional count labels
Set showCounts: true to render cells as HTML elements instead of SVG. Counts
stay hidden by default for compact graphs. Add countFontSize to show animated
in-cell count labels powered by slot-text.
import "@danielwh2/contribution-graph/style.css";
import "slot-text/style.css";
import { contributionGraph } from "@danielwh2/contribution-graph";
const graph = contributionGraph(el, data, {
showCounts: true,
countFontSize: 9,
until: "2026-06-22",
});
graph.set(newData);Text colors auto-contrast with the cell background. Override with countColors:
contributionGraph(el, data, {
showCounts: true,
countFontSize: 9,
colors: ["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"],
countColors: ["#57606a", "#1f2328", "#1f2328", "#ffffff", "#ffffff"],
});📅 Date handling
All date math uses UTC so graphs render identically in every timezone.
Weeks start on Sunday (GitHub convention). The range ends at until and is
snapped back so the first column begins on a Sunday. Days beyond until in the
final week are omitted.
Data entries are matched by YYYY-MM-DD; days with no entry default to count 0.
Human-readable labels (tooltip/click day.dateLabel) are formatted with dayjs
in UTC mode via the exported formatHumanDate helper.
📝 Notes
- Browser-only DOM utility with no framework runtime dependency.
- Smooth tooltips and optional in-cell count labels are powered by
slot-text. - React, Vue, Solid, and Svelte are optional peer dependencies — plain JS users don't need them.
- Import
@danielwh2/contribution-graph/style.cssonce before rendering. Importslot-text/style.csswhen you use the animated tooltip or count label styles. - Low-level helpers also exported:
buildGraph,clearGraph,buildHtmlGraph,clearHtmlGraph,buildHudGraph,clearHudGraph,computeGrid,levelForCount,parseDate,formatDate,formatHumanDate,resolveOptions,fetchGitHubContributions.
License
MIT
