Streak Calendar
trophy/streak-calendarFreeComponent
React streak calendar for gamification—week, month, and git-style grids to visualize consistency. Native support for streak freezes. Ideal streak UI design for habit tracking and personal progress. Built on shadcn/ui and Tailwind CSS. Open source.
Live preview
live · rendered by the registry
Rendered by trophy on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://ui.trophy.so/r/streak-calendar.jsonSource
1"use client"23import * as React from "react"4import * as TooltipPrimitive from "@radix-ui/react-tooltip"5import { Check, Snowflake } from "lucide-react"67import { cn } from "@/lib/utils"89// Types (inlined - only fields used by this component)10interface StreakPeriod {11 periodStart: string12 periodEnd: string13 usedFreeze?: boolean14}1516function formatDateKey(date: Date): string {17 const year = date.getFullYear()18 const month = String(date.getMonth() + 1).padStart(2, "0")19 const day = String(date.getDate()).padStart(2, "0")20 return `${year}-${month}-${day}`21}2223// Helper to check if a date falls within a streak period24function isDateInPeriod(date: Date, period: StreakPeriod): boolean {25 const d = formatDateKey(date)26 return d >= period.periodStart && d <= period.periodEnd27}2829// Helper to check if date used a freeze30function didUseFreezeOnDate(date: Date, periods: StreakPeriod[]): boolean {31 for (const period of periods) {32 if (isDateInPeriod(date, period) && period.usedFreeze) {33 return true34 }35 }36 return false37}3839// Helper to check if date was active in streak40function wasDateActive(date: Date, periods: StreakPeriod[]): boolean {41 for (const period of periods) {42 if (isDateInPeriod(date, period)) {43 return true44 }45 }46 return false47}4849// Get days in month50function getDaysInMonth(year: number, month: number): number {51 return new Date(year, month + 1, 0).getDate()52}5354// Get day of week for first day of month (0 = Sunday)55function getFirstDayOfMonth(year: number, month: number): number {56 return new Date(year, month, 1).getDay()57}5859// Props60interface StreakCalendarProps extends React.HTMLAttributes<HTMLDivElement> {61 /** Streak periods */62 streak: StreakPeriod[]63 /** Calendar layout variant */64 view?: "week" | "month" | "year"65 /** Month to display (default: current month) */66 month?: Date67 /** Date used for week view (default: today) */68 referenceDate?: Date69 /** Show freeze indicators */70 showFreezes?: boolean71 /** Start of week: 0 = Sunday, 1 = Monday */72 startOfWeek?: 0 | 173 /** Callback when a day is clicked */74 onDayClick?: (date: Date, wasActive: boolean) => void75}7677const WEEKDAYS_SUNDAY = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]78const WEEKDAYS_MONDAY = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"]7980const TooltipProvider = TooltipPrimitive.Provider81const Tooltip = TooltipPrimitive.Root82const TooltipTrigger = TooltipPrimitive.Trigger83const TooltipContent = React.forwardRef<84// … truncated
What it pulls in
npm packages
Files it writes
More from trophy
All 18 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Achievement Badgeachievement-badge | trophy | Components | Free | 1 dep | |
| Achievement Cardachievement-card | trophy | Components | Free | no deps | |
| Achievement Gridachievement-grid | trophy | Components | Free | 2 deps | |
| Achievement Listachievement-list | trophy | Components | Free | 2 deps | |
| Achievement Unlockedachievement-unlocked | trophy | Components | Free | 1 dep | |
| Leaderboard Cardleaderboard-card | trophy | Components | Free | no deps | |
| Leaderboard Podiumleaderboard-podium | trophy | Components | Free | 2 deps | |
| Leaderboard Rankingsleaderboard-rankings | trophy | Components | Free | 2 deps |
