A flexible and customizable React heatmap component for displaying year-based contribution calendars, similar to GitHub's contribution graph.
- Flexible Year Display: Show any year's calendar in a heatmap format
- Customizable Week Start: Choose between Sunday or Monday as the first day of the week
- Fully Customizable Rendering: Override default rendering for day cells, month labels, and weekday labels
- Responsive Sizing: Configure cell size and gaps to fit your design
- TypeScript Support: Full TypeScript definitions included
- Lightweight: Built with React and dayjs
npm install @zhongyao/heatmapThis package requires the following peer dependencies:
{
"react": "^16.18.0",
"dayjs": "^1.11.19"
}import { Heatmap } from "@zhongyao/heatmap";
function App() {
return <Heatmap year={2025} weekStartDay="sunday" />;
}| Prop | Type | Default | Description |
|---|---|---|---|
year |
number |
2025 |
The year to display |
weekStartDay |
'sunday' | 'monday' |
'sunday' |
First day of the week |
cellSize |
number |
16 |
Base size of cells and labels in pixels |
gap |
number |
4 |
Spacing between cells and labels in pixels |
containerGap |
number |
18 |
Gap between weekday labels and the main grid |
className |
string |
'' |
Additional className for the wrapper |
renderDay |
(props: DayCellProps) => React.ReactNode |
undefined |
Custom day cell renderer |
renderMonth |
(props: MonthLabelProps) => React.ReactNode |
undefined |
Custom month label renderer |
renderWeekday |
(props: WeekdayLabelProps) => React.ReactNode |
undefined |
Custom weekday label renderer |
weekDayLabelLayout |
'left' | 'center' | 'right' |
'left' |
Alignment of weekday labels |
weekDayLabelStyle |
CSSProperties |
undefined |
Custom styles for weekday labels |
The renderDay prop allows you to customize how each day cell is rendered. You receive a DayCellProps object:
interface DayCellProps {
date: Dayjs | null; // Date object (null for empty placeholders)
dayOfYear: number | null; // Day of year (1-366, null for placeholders)
colIndex: number; // 0-indexed column
rowIndex: number; // 0-indexed row (0-6)
isEmpty: boolean; // True for year start/end placeholders
}Example:
<Heatmap
year={2025}
renderDay={({ date, isEmpty }) => {
if (isEmpty) return <div style={{ width: 16, height: 16 }} />;
// Custom logic based on your data
const contribution = getContributionCount(date);
const intensity = Math.min(contribution / 10, 1);
return (
<div
style={{
width: 16,
height: 16,
backgroundColor: `rgba(0, 255, 0, ${intensity})`,
borderRadius: 2,
}}
title={`${date.format("YYYY-MM-DD")}: ${contribution} contributions`}
/>
);
}}
/>The renderMonth prop customizes month labels:
interface MonthLabelProps {
index: number; // 0-indexed month (0-11)
label: string; // Default label (e.g., "Jan")
startCol: number; // 1-indexed starting column
span: number; // Number of columns this month spans
}Example:
<Heatmap
year={2025}
renderMonth={({ label, span }) => (
<div
style={{
gridColumn: `span ${span}`,
height: 16,
display: "flex",
alignItems: "center",
fontWeight: "bold",
fontSize: 14,
}}
>
{label}
</div>
)}
/>The renderWeekday prop customizes weekday labels:
interface WeekdayLabelProps {
index: number; // 0-indexed weekday (0-6)
label: string; // Default label (e.g., "Mon")
layout?: "left" | "center" | "right"; // Label alignment
style?: CSSProperties; // Custom styles
}Example:
<Heatmap
year={2025}
weekDayLabelLayout="center"
renderWeekday={({ label }) => (
<div
style={{
height: 16,
display: "flex",
alignItems: "center",
justifyContent: "center",
fontWeight: 600,
color: "#374151",
}}
>
{label.charAt(0)} {/* Show only first letter */}
</div>
)}
/>Here's a complete example with custom styling and data:
import { Heatmap, DayCellProps } from "@zhongyao/heatmap";
import { Dayjs } from "dayjs";
// Your contribution data
const contributions: Record<string, number> = {
"2025-01-15": 5,
"2025-01-16": 12,
"2025-02-01": 8,
// ... more data
};
function ContributionHeatmap() {
const renderDay = ({ date, isEmpty }: DayCellProps) => {
if (isEmpty) {
return <div style={{ width: 12, height: 12 }} />;
}
const dateKey = date!.format("YYYY-MM-DD");
const count = contributions[dateKey] || 0;
// Calculate color intensity
const getColor = (count: number) => {
if (count === 0) return "#ebedf0";
if (count < 5) return "#9be9a8";
if (count < 10) return "#40c463";
if (count < 15) return "#30a14e";
return "#216e39";
};
return (
<div
style={{
width: 12,
height: 12,
backgroundColor: getColor(count),
borderRadius: 2,
cursor: "pointer",
}}
title={`${dateKey}: ${count} contributions`}
/>
);
};
return (
<Heatmap
year={2025}
weekStartDay="monday"
cellSize={12}
gap={3}
renderDay={renderDay}
className="my-heatmap"
/>
);
}The component uses Tailwind CSS classes by default, but you can fully customize styling through:
- Props: Use
cellSize,gap,containerGapfor spacing - Custom Renderers: Full control over rendering via
renderDay,renderMonth,renderWeekday - CSS Classes: Add custom classes via the
classNameprop
MIT
Contributions are welcome! Please feel free to submit a Pull Request.
