OpenSourceUIOpenSourceUIOpensource UI
GitHubTwitter/X
  1. Home
  2. Components
  3. Widgets
  4. Daily Activity Calendar
Back to WidgetsComponents / Widgets

Daily Activity Calendar

Free Widgets React component — DailyActivityCalendarWidget. MIT licensed, copy-paste ready for Next.js and Tailwind CSS.

Month grid for the current month — today is highlighted in orange, with a short emerald streak on prior days. Pass activeDays or highlightDay to override.

Preview

Daily Activity

AugAugust 2026
SMTWTFS
12345678910111213141516171819202122232425262728293031

Setup

How to use

Free for personal and commercial use. No UI attribution required. Include the MIT copyright notice when copying component source into your project. Read the MIT license.

  1. 1

    Run in your terminal:

    $npm install clsx tailwind-merge lucide-react
  2. 2

    Copy lib/cn.ts below. Skip if you already have cn().

  3. 3

    Copy the code below and create components/widgets/daily-activity-calendar-widget.tsx in your project.

  4. 4

    Import and render:

    Example

    import { DailyActivityCalendarWidget } from "@/components/widgets/daily-activity-calendar-widget";

    <DailyActivityCalendarWidget activeDays={[12, 13, 14, 15, 16]} highlightDay={17} />

lib/cn.ts

1
2
3
4
5
6
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
 
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

components/widgets/daily-activity-calendar-widget.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
"use client";
 
import {
forwardRef,
useMemo,
type ComponentPropsWithoutRef,
} from "react";
 
import { cn } from "@/lib/cn";
import { Clock } from "lucide-react";
 
/** Monthly activity calendar — highlight active days and a selected date. */
const WEEKDAY_HEADERS = [
{ id: "sun", label: "S" },
{ id: "mon", label: "M" },
{ id: "tue", label: "T" },
{ id: "wed", label: "W" },
{ id: "thu", label: "T" },
{ id: "fri", label: "F" },
{ id: "sat", label: "S" },
] as const;
 
function daysInMonth(year: number, month: number) {
return new Date(year, month + 1, 0).getDate();
}
 
/** Up to six day numbers before today in the same month — demo streak. */
function defaultActiveDays(today: Date) {
const day = today.getDate();
const start = Math.max(1, day - 6);
return Array.from({ length: day - start }, (_, index) => start + index);
}
 
// activeDays — emerald dots; highlightDay — orange selected day (defaults to today)
export type DailyActivityCalendarWidgetProps = Readonly<
{
activeDays?: number[];
highlightDay?: number;
} & ComponentPropsWithoutRef<"div">
>;
 
export const DailyActivityCalendarWidget = forwardRef<
HTMLDivElement,
DailyActivityCalendarWidgetProps
>(
(
{
className,
activeDays: activeDaysProp,
highlightDay: highlightDayProp,
...props
},
ref,
) => {
const today = useMemo(() => {
const date = new Date();
date.setHours(0, 0, 0, 0);
return date;
}, []);
 
const year = today.getFullYear();
const monthIndex = today.getMonth();
const daysCount = daysInMonth(year, monthIndex);
const leadingBlanks = new Date(year, monthIndex, 1).getDay();
 
const monthLabel = today.toLocaleDateString("en-US", {
month: "long",
year: "numeric",
});
const monthBadge = today.toLocaleDateString("en-US", { month: "short" });
 
const activeDays = activeDaysProp ?? defaultActiveDays(today);
const highlightDay = highlightDayProp ?? today.getDate();
 
return (
<div
ref={ref}
data-slot="daily-activity-calendar-widget"
data-year={year}
className={cn(
"flex h-44 w-44 flex-col overflow-hidden rounded-3xl bg-neutral-900 p-3 font-sans text-white shadow-lg",
className,
)}
{...props}
>
<div className="mb-3 flex items-center justify-between">
<p className="text-sm font-semibold">Daily Activity</p>
<Clock size={14} className="text-neutral-400" />
</div>
 
<div className="mb-2 flex items-center justify-between">
<span className="rounded-full bg-neutral-700 px-2 py-0.5 text-[9px]">
{monthBadge}
</span>
<span className="text-[10px] text-neutral-400">{monthLabel}</span>
</div>
 
<div className="mb-1 grid grid-cols-7 gap-0.5 text-center text-[8px] text-neutral-500">
{WEEKDAY_HEADERS.map(({ id, label }) => (
<span key={id}>{label}</span>
))}
</div>
 
<div className="grid grid-cols-7 gap-0.5 text-center">
{Array.from({ length: leadingBlanks }, (_, slot) => (
<span key={`start-${slot}`} />
))}
{Array.from({ length: daysCount }, (_, dayIndex) => dayIndex + 1).map(
(day) => {
const isActive = activeDays.includes(day);
const isHighlight = day === highlightDay;
const isIdle = !isActive && !isHighlight;
 
return (
<span
key={day}
className={cn(
"flex h-5 w-5 items-center justify-center rounded-full text-[9px] transition-all duration-200 ease-out",
isActive &&
"scale-100 bg-emerald-500 font-semibold text-white",
isHighlight &&
"scale-100 bg-orange-500 font-semibold text-white",
isIdle && "text-neutral-400",
)}
>
{day}
</span>
);
},
)}
</div>
</div>
);
},
);
 
DailyActivityCalendarWidget.displayName = "DailyActivityCalendarWidget";
PreviousNext

On this

page

Jump to a section on this page.

  • lib/cn.ts
  • components/widgets/daily-activity-calendar-widget.tsx

Sponsor spot · demo preview

Logo

Your brand name

Your tagline or category

A short pitch about your product or service goes here. This is a preview of how sponsor cards will look in this sidebar.

Your call to action

This slot is available. Sponsor Opensource UI and reach developers browsing components every day.