A modern, customizable calendar heatmap component built on top of react-day-picker following shadcn/ui patterns.
Accessible. Unstyled. Customizable. Open Source.
- π¨ Fully Customizable - Style with Tailwind CSS classes
- π Multiple Data Modes - Use direct date arrays or weighted dates with auto-categorization
- π’ Multi-month Support - Display any number of months
- βΏ Accessible - Built on react-day-picker with full keyboard navigation
- π― Type Safe - Written in TypeScript with full type definitions
- π Preset Variants - GitHub streaks, temperature heatmaps, rainbow colors, and more
Check out the live demo at shadcn-calendar-heatmap.vercel.app
This component follows the shadcn/ui philosophy - copy the component directly into your project.
npm install react-day-picker date-fns lucide-react
# or
yarn add react-day-picker date-fns lucide-react
# or
pnpm add react-day-picker date-fns lucide-reactCopy components/ui/calendar-heatmap.tsx into your project's components directory.
Make sure you have the cn utility function (standard in shadcn/ui projects):
// lib/utils.ts
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}import { CalendarHeatmap } from "@/components/ui/calendar-heatmap"
export default function MyComponent() {
return (
<CalendarHeatmap
variantClassnames={[
"text-white hover:text-white bg-green-400 hover:bg-green-400",
"text-white hover:text-white bg-green-500 hover:bg-green-500",
"text-white hover:text-white bg-green-700 hover:bg-green-700",
]}
datesPerVariant={[
[new Date('Jan 1, 2024'), new Date('Jan 15, 2024')],
[new Date('Jun 12, 2024'), new Date('July 1, 2024')],
[new Date('Jan 19, 2024'), new Date('Apr 14, 2024')],
]}
/>
)
}Pass dates with numeric weights, and the component auto-categorizes them:
<CalendarHeatmap
variantClassnames={[
"text-white hover:text-white bg-blue-300 hover:bg-blue-300",
"text-white hover:text-white bg-green-500 hover:bg-green-500",
"text-white hover:text-white bg-amber-400 hover:bg-amber-400",
"text-white hover:text-white bg-red-700 hover:bg-red-700",
]}
weightedDates={[
{ date: new Date('Jan 1, 2024'), weight: 2 },
{ date: new Date('Jun 12, 2024'), weight: 8 },
{ date: new Date('Apr 19, 2024'), weight: 13.5 },
]}
/><CalendarHeatmap
numberOfMonths={3}
variantClassnames={[...]}
datesPerVariant={[...]}
/>| Prop | Type | Required | Description |
|---|---|---|---|
variantClassnames |
string[] |
β | Array of Tailwind CSS classes for each intensity level |
datesPerVariant |
Date[][] |
β‘ | 2D array where each inner array contains dates for that variant |
weightedDates |
WeightedDateEntry[] |
β‘ | Array of { date: Date, weight: number } objects |
numberOfMonths |
number |
β | Number of months to display (default: 1) |
showOutsideDays |
boolean |
β | Show days from adjacent months (default: true) |
β‘ You must provide either
datesPerVariantORweightedDates, not both.
The component also accepts all props from react-day-picker.
type WeightedDateEntry = {
date: Date
weight: number
}const Heatmap = [
"text-white hover:text-white bg-blue-300 hover:bg-blue-300", // Cold
"text-white hover:text-white bg-green-500 hover:bg-green-500", // Mild
"text-white hover:text-white bg-amber-400 hover:bg-amber-400", // Warm
"text-white hover:text-white bg-red-700 hover:bg-red-700", // Hot
]const Rainbow = [
"text-white hover:text-white bg-violet-400 hover:bg-violet-400",
"text-white hover:text-white bg-indigo-400 hover:bg-indigo-400",
"text-white hover:text-white bg-blue-400 hover:bg-blue-400",
"text-white hover:text-white bg-green-400 hover:bg-green-400",
"text-white hover:text-white bg-yellow-400 hover:bg-yellow-400",
"text-white hover:text-white bg-orange-400 hover:bg-orange-400",
"text-white hover:text-white bg-red-400 hover:bg-red-400",
]Contributions are welcome! Feel free to open an issue or submit a pull request.
MIT Β© Gurbaaz Singh Nandra