A GitHub-style heatmap calendar component built with React, Tailwind CSS, and shadcn/ui.
✔️ Copy-paste friendly ✔️ No charting libraries ✔️ Light & dark mode ✔️ Axis labels (months + weekdays) ✔️ Domain-agnostic (fitness, business, IoT, learning, etc.)
- 📅 Calendar heatmap (GitHub-style)
- 🧭 Axis labels
- Months on top
- Weekdays on the left
- 🎨 Theme presets with copyable CSS variables
- 🌗 Light / dark mode ready
- 🧩 Domain-agnostic
- fitness activity
- business metrics
- support tickets
- learning progress
- IoT events
- 🧠 Smart data merging (duplicate dates are summed)
- 🧪 Tooltips & click handlers
- ⚡ No canvas, SVG, or heavy chart libraries
Live demo available in the repository / website.
This component is designed to be copied directly into your project (like shadcn blocks).
npm install clsx tailwind-merge lucide-reactnpx shadcn@latest add card tooltipCopy the following file into your project:
components/heatmap-calendar.tsx
No provider. No configuration. No build step.
| Prop | Type | Default | Description |
|---|---|---|---|
title |
string |
"Activity" |
Title displayed above the heatmap |
data |
HeatmapDatum[] |
required | Daily aggregated data |
rangeDays |
number |
365 |
Number of days to render (ending at endDate) |
endDate |
Date |
new Date() |
Last date of the heatmap range |
weekStartsOn |
0 | 1 |
1 |
Week start (0 = Sunday, 1 = Monday) |
cellSize |
number |
12 |
Cell size in pixels |
cellGap |
number |
3 |
Gap between cells in pixels |
axisLabels |
boolean | AxisLabelConfig |
true |
Show or configure axis labels |
legend |
boolean | LegendConfig |
true |
Show or configure the legend |
palette |
string[] |
— | Custom color palette for intensity levels |
levelClassNames |
string[] |
semantic defaults | Tailwind classes for intensity levels |
onCellClick |
(cell: HeatmapCell) => void |
— | Called when a cell is clicked |
renderTooltip |
(cell: HeatmapCell) => ReactNode |
— | Custom tooltip renderer |
renderLegend |
(args) => ReactNode |
— | Fully custom legend renderer |
className |
string |
— | Additional class names for the card |
The heatmap expects daily aggregated data.
export type HeatmapDatum = {
date: string | Date; // "YYYY-MM-DD" recommended
value: number; // daily intensity
meta?: unknown; // optional metadata
};Example
const data = [
{ date: "2025-01-01", value: 3 },
{ date: "2025-01-02", value: 0 },
{ date: "2025-01-03", value: 8 },
];Notes
- Duplicate dates are automatically merged (values are summed)
- Use "YYYY-MM-DD" strings to avoid timezone issues
- Values can represent anything: minutes, orders, tickets, events, etc.
import { HeatmapCalendar } from "@/components/heatmap-calendar"
export default function Example() {
return (
<HeatmapCalendar
title="Activity"
data={data}
axisLabels
/>
)
}Axis labels are enabled by default and show:
- 📅 Months on top
- 📆 Weekdays on the left
<HeatmapCalendar data={data} axisLabels />Customize axis labels
<HeatmapCalendar
data={data}
axisLabels={{
showMonths: true,
showWeekdays: true,
weekdayIndices: [1, 3, 5], // Mon / Wed / Fri
monthFormat: "short", // "short" | "long" | "numeric"
minWeekSpacing: 3,
}}
/>Disable completely:
<HeatmapCalendar data={data} axisLabels={false} /><HeatmapCalendar
data={data}
onCellClick={(cell) => {
console.log(cell.date, cell.value, cell.meta)
}}
renderTooltip={(cell) => (
<div>
<div className="font-medium">{cell.value} events</div>
<div className="text-muted-foreground">{cell.label}</div>
</div>
)}
/>Contributions are welcome!
- Bug fixes
- Performance improvements
- Documentation improvements
- New examples
Feel free to open an issue or pull request.
MIT © Minh (Marcus) Nguyen
Made with ❤️ by Minh (Marcus) Nguyen