Activity heatmap
See a year of activity at a glance, one square per day.
datachartcalendar
Preview
Activity heatmap preview
Live · keyboard ready
commits in 1,487 commits in May to August 2026
Darker squares mean more commits. Levels: no commits, 1 to 7 commits, 8 to 13 commits, 14 to 20 commits, 21 or more commits.
When to use
- Daily rhythm over a long range, like contributions or workouts.
- Views where streaks and quiet weeks matter more than exact comparison.
When not to use
- Use bar-chart when the exact comparison is the point.
- Use calendar to pick dates.
shadcn CLI
Also installs (1)
- manicat-foundation
Manual install(+)
registry.jsonjson
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "activity-heatmap",
"type": "registry:ui",
"title": "Activity heatmap",
"description": "See a year of activity at a glance, one square per day.",
"categories": [
"data"
],
"dependencies": [
"motion"
],
"registryDependencies": [
"https://manicat.viraza.net/r/manicat-foundation.json"
],
"files": [
{
"path": "registry/components/activity-heatmap/activity-heatmap.tsx",
"type": "registry:component",
"target": "@components/manicat/activity-heatmap/activity-heatmap.tsx"
},
{
"path": "registry/components/activity-heatmap/activity-heatmap.module.css",
"type": "registry:component",
"target": "@components/manicat/activity-heatmap/activity-heatmap.module.css"
}
],
"docs": "Docs and live preview: https://uiarc.dev/components/activity-heatmap",
"meta": {
"tier": "free",
"kind": "component",
"docs": "https://manicat.viraza.net/components/activity-heatmap",
"markdown": "https://uiarc.dev/components/activity-heatmap/markdown",
"tags": [
"data",
"chart",
"calendar"
]
}
}Usage
Install the component, then copy this into your app. Change the content and the props to fit your screen.
example.tsxtsx
"use client";
import { ActivityHeatmap } from "@/components/manicat/activity-heatmap/activity-heatmap";
import type { ActivityDay } from "@/components/manicat/activity-heatmap/activity-heatmap";
function iso(date: Date): string {
return date.toISOString().slice(0, 10);
}
const heatmapDays: ActivityDay[] = Array.from({ length: 140 }, (_, index) => {
const date = new Date(Date.UTC(2026, 4, 4, 12));
date.setUTCDate(date.getUTCDate() + index);
const count = Math.floor(((index * 37) % 31) * (index % 5 === 0 ? 1.4 : 0.6));
return { date: iso(date), count: Math.min(count, 26) };
});
export function ActivityHeatmapExample() {
// Size containers have no width of their own: give the demo the whole row.
return <div className="w-full"><ActivityHeatmap days={heatmapDays} label="Commits" period="May to August 2026" unit={{ one: "commit", other: "commits" }} /></div>;
}
Examples
Real screens rather than isolated props. Switch to Code to copy a whole example into your project.
Contribution graph A GitHub-style calendar of commits with a plural-aware unit in the tooltip.
Contribution graph
A GitHub-style calendar of commits with a plural-aware unit in the tooltip. preview
Live · keyboard ready
commits in 1,487 commits in May to August 2026
Darker squares mean more commits. Levels: no commits, 1 to 7 commits, 8 to 13 commits, 14 to 20 commits, 21 or more commits.
contribution-graph-a-github-style-calendar-of-commits-with-a-plural-aware-unit-in-the-tooltip-.tsxtsx
"use client";
import { ActivityHeatmap } from "@/components/manicat/activity-heatmap/activity-heatmap";
import type { ActivityDay } from "@/components/manicat/activity-heatmap/activity-heatmap";
const DAY = 86_400_000;
const commits: ActivityDay[] = Array.from({ length: 140 }, (_, index) => {
const date = new Date(Date.UTC(2026, 4, 4, 12) + index * DAY);
const count = Math.floor(((index * 37) % 31) * (index % 5 === 0 ? 1.4 : 0.6));
return { date: date.toISOString().slice(0, 10), count: Math.min(count, 26) };
});
export function ActivityHeatmapCommitsExample() {
return <ActivityHeatmap days={commits} label="Commits" period="May to August 2026" unit={{ one: "commit", other: "commits" }} />;
}
Keyboard interactions
| Keys | Action |
|---|---|
| ArrowUp / ArrowDown | Moves one day back or forward. |
| ArrowLeft / ArrowRight | Moves one week back or forward. |
| Home / End | Jumps to the first or last day. |
| Enter / Space | Selects the focused day. |
| Escape | Hides the tooltip. |
Accessibility
- The grid is role="grid" with labelled gridcells and a hidden legend explaining the levels.
- Legend swatches are toggle buttons with aria-pressed that highlight days of one level.
- The total and hovered day are announced through status and polite live regions; the tooltip itself is aria-hidden.
Motion
- Cells wave in once on view and a new range recolors the grid in a sweep.
- The tooltip glides between cells and its text rolls; the total counts to new values.
- Reduced motion drops the wave, sweep, and glide in favor of short fades.
Responsive behavior
- Cells scale between 9px and 15px with the container, then the grid scrolls horizontally from the newest week.
- Below a 440px container the caption switches to a short form.
- On touch the tooltip lingers after a tap instead of hiding at once.
Performance
- Every day is its own cell, so a year is about 370 elements; the reveal wave is capped in total time.
- One shared tooltip serves the whole grid.
Notes for AI
- Use when rhythm, streaks, and quiet weeks matter more than exact comparison. Use bar-chart when the exact comparison is the point.
- Pass a range switch through actions and swap days to change the period.