Activity Graph

GitHub-style activity heatmap that visualizes daily counts as a color-intensity grid with month labels, day labels, and a Less/More legend. Includes a helper to fetch real GitHub contribution data.

Preview

1,016 contributions in the last year

Installation

$ shadcn add https://ui.justinlevine.me/r/activity-graph.json

Usage

import { ActivityGraph } from "@/components/activity-graph"
<ActivityGraph data={[{ date: "2026-03-11", count: 5 }, ...]} />

Pass an array of { date: string; count: number } entries. The component builds the trailing-week grid automatically. Dates not present in the array render as zero-count cells.

Playground

Props

import { ActivityGraph } from "@/components/activity-graph"

<ActivityGraph />

GitHub helper

The included fetchGitHubContributions function scrapes the public GitHub contributions page for any username — no API key required. It returns per-day counts and the yearly total.

import { fetchGitHubContributions } from "@/lib/github"
const contributions = await fetchGitHubContributions("octocat")
<ActivityGraph data={contributions.entries} />

The response is cached for 1 hour via Next.js ISR. Works as a server-side call in a server component or route handler.

Examples

Activity patterns

Sparse activity

Half year (26 weeks)

Custom color scales

Blue

Amber

Purple

Fixed block size

Fixed 14px (scrollable if wider than container)

API Reference

ActivityGraph

PropType

ActivityEntry

PropType

fetchGitHubContributions

PropType

Notes

  • Client component. Uses "use client" for a ResizeObserver that auto-sizes blocks to fit the container. Hover tooltips use native title attributes.
  • Auto-fit. By default the block size is computed from the container width so the graph always fits without scrolling. Pass a fixed blockSize to opt into horizontal scrolling instead.
  • GitHub scraping. The fetch helper scrapes GitHub's public contributions HTML page. No API key needed. Cached for 1 hour via ISR. If GitHub changes their markup, the parser may need updating.
  • Intensity mapping. Counts are mapped to four non-zero levels relative to the maximum count in the data. The thresholds are 25%, 50%, 75%, and 100% of the max.
  • No dependencies. This component uses only React, Tailwind, and the cn utility. No charting libraries or external packages.