Magic Calendar
A polished interactive calendar for special dates, animated selection states, and celebratory moments without sacrificing clarity.
Preview
Live interactive preview.
Installation
Add this component to your project using the CLI:
npx -y vui-registry-cli-v1@latest add magic-calendarmagic-calendar-utils.ts
export const MONTHS = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
]
export const YEARS = Array.from({ length: 101 }, (_, i) => 1950 + i)
export const daysMatrix = (y: number, m: number) => {
const first = new Date(y, m, 1).getDay()
const last = new Date(y, m + 1, 0).getDate()
const cells: { day: number | null; key: string }[] = []
for (let i = 0; i < first; i++) cells.push({ day: null, key: `p-${i}` })
for (let d = 1; d <= last; d++) cells.push({ day: d, key: `${y}-${m}-${d}` })
return cells
}
Source Code
'use client'
import React, { useMemo, useState, useRef } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { ChevronLeft, ChevronRight, MapPin, Sparkles } from 'lucide-react'
import { cn } from '@/lib/utils'
import confetti from 'canvas-confetti'
import { MONTHS, YEARS, daysMatrix } from './magic-calendar-utils'
export default function MagicCalendar() {
const [viewDate, setViewDate] = useState(new Date(2002, 4, 1))
const [selectedDate, setSelectedDate] = useState<Date | null>(null)
const [isPopupOpen, setIsPopupOpen] = useState(false)
const [showDial, setShowDial] = useState<'month' | 'year' | null>(null)
const y = viewDate.getFullYear()
const m = viewDate.getMonth()
const cells = useMemo(() => daysMatrix(y, m), [y, m])
const fireConfetti = () => {
confetti({
particleCount: 150,
spread: 70,
origin: { y: 0.6 },
colors: ['#38bdf8', '#818cf8', '#ffffff']
});
};
const handleDateClick = (d: number) => {
const newDate = new Date(y, m, d)
setSelectedDate(newDate)
setIsPopupOpen(true)
if (d === 11 && m === 4 && y === 2002) fireConfetti();
}
const triggerMagic = () => {
setViewDate(new Date(2002, 4, 1));
setSelectedDate(new Date(2002, 4, 11));
setIsPopupOpen(true);
setTimeout(fireConfetti, 50);
}
const USER_IMAGE = "https://i.postimg.cc/3xgQH76g/Whats-App-Image-2026-02-19-at-8-23-43-PM.jpg";
const isVikasBirthday = selectedDate?.getDate() === 11 &&
selectedDate?.getMonth() === 4 &&
selectedDate?.getFullYear() === 2002;
return (
<div className="relative flex flex-col items-center justify-center p-8 min-h-[700px] select-none font-sans">
<div className="relative p-[3px] rounded-[34px] bg-gradient-to-b from-neutral-200 to-neutral-100 dark:from-neutral-800 dark:to-neutral-900 shadow-2xl">
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/50 to-transparent opacity-50 blur-sm" />
<motion.div
className="relative w-full max-w-sm overflow-hidden rounded-[32px] bg-card/90 backdrop-blur-xl border border-white/20 shadow-inner"
>
{/* Inner Double Border Effect */}
<div className="absolute inset-[3px] rounded-[29px] border border-neutral-100/50 dark:border-white/5 pointer-events-none z-0" />
<div className="relative flex items-center justify-between p-6 z-30">
<div className="relative space-y-1">
<div className="flex gap-1 items-baseline">
<button
onClick={() => setShowDial(showDial === 'month' ? null : 'month')}
className={cn("text-xl font-semibold tracking-tight transition-all hover:text-sky-400 active:scale-95", showDial === 'month' && "text-sky-500")}
>
{MONTHS[m]}
</button>
<button
onClick={() => setShowDial(showDial === 'year' ? null : 'year')}
className={cn("text-sm font-bold opacity-30 transition-all hover:opacity-100 hover:text-sky-400 active:scale-95", showDial === 'year' && "opacity-100 text-sky-500")}
>
{y}
</button>
</div>
<p className="text-[10px] font-bold text-muted-foreground/50 uppercase tracking-[0.2em]">
{showDial ? "Pick and close" : "Select a date"}
</p>
<AnimatePresence>
{showDial && (
<>
<motion.div
initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}
onClick={() => setShowDial(null)}
className="fixed inset-0 z-40"
/>
<motion.div
initial={{ opacity: 0, y: 10, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 10, scale: 0.95 }}
className="absolute top-full left-0 mt-2 w-48 z-50 overflow-hidden rounded-2xl border border-white/10 bg-background/80 backdrop-blur-2xl shadow-2xl"
>
<div className="h-48 overflow-y-auto scrollbar-hide py-2 px-1">
{(showDial === 'month' ? MONTHS : YEARS).map((item, idx) => (
<button
key={item}
onClick={() => {
if (showDial === 'month') setViewDate(new Date(y, idx, 1));
else setViewDate(new Date(Number(item), m, 1));
setShowDial(null);
}}
className={cn(
"w-full px-4 py-2 text-left text-xs font-bold uppercase tracking-wider transition-colors rounded-lg mb-1",
(showDial === 'month' ? m === idx : y === Number(item))
? "bg-sky-500 text-white"
: "text-foreground/60 hover:bg-foreground/5 hover:text-foreground"
)}
>
{item}
</button>
))}
</div>
</motion.div>
</>
)}
</AnimatePresence>
</div>
<div className="flex gap-1 bg-foreground/5 p-1 rounded-full relative z-20">
<button onClick={() => setViewDate(new Date(y, m - 1, 1))} className="p-2 hover:bg-background rounded-full transition-colors">
<ChevronLeft className="w-4 h-4" />
</button>
<button onClick={() => setViewDate(new Date(y, m + 1, 1))} className="p-2 hover:bg-background rounded-full transition-colors">
<ChevronRight className="w-4 h-4" />
</button>
</div>
</div>
<div className="px-6 pb-8 pt-2">
<div className="grid grid-cols-7 mb-4">
{['S', 'M', 'T', 'W', 'T', 'F', 'S'].map((day, idx) => (
<span
key={day + '-' + idx}
className="text-center text-[10px] font-bold text-muted-foreground/40"
>
{day}
</span>
))}
</div>
<div className="grid grid-cols-7 gap-y-2">
{cells.map((cell) => {
const isSelected = selectedDate?.getDate() === cell.day && selectedDate?.getMonth() === m && selectedDate?.getFullYear() === y;
return (
<div key={cell.key} className="relative flex items-center justify-center aspect-square">
{cell.day && (
<motion.button
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.95 }}
onClick={() => handleDateClick(cell.day!)}
className={cn(
"relative z-10 w-10 h-10 rounded-2xl text-sm font-medium transition-colors flex items-center justify-center",
isSelected ? "text-background" : "text-foreground/70 hover:text-foreground"
)}
>
{cell.day}
{isSelected && (
<motion.div
layoutId="active-pill"
className="absolute inset-0 bg-foreground rounded-2xl -z-10 shadow-lg"
/>
)}
</motion.button>
)}
</div>
)
})}
</div>
</div>
</motion.div>
</div>
<button
onClick={triggerMagic}
className="mt-8 text-xs font-medium text-muted-foreground hover:text-foreground transition-colors"
>
Jump to 11 May 2002
</button>
<AnimatePresence>
{isPopupOpen && selectedDate && (
<div className="absolute inset-0 z-50 flex items-center justify-center p-4">
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} onClick={() => setIsPopupOpen(false)} className="absolute inset-0 bg-background/40 backdrop-blur-md" />
<motion.div
initial={{ scale: 0.95, opacity: 0, y: 10 }}
animate={{ scale: 1, opacity: 1, y: 0 }}
exit={{ scale: 0.95, opacity: 0, y: 10 }}
transition={{ type: "spring", stiffness: 400, damping: 30 }}
className={cn(
"relative w-full max-w-[280px] overflow-hidden rounded-3xl p-6 shadow-xl",
isVikasBirthday ? "bg-zinc-950 border border-zinc-800 shadow-black/50" : "bg-card border border-border"
)}
>
<div className="relative z-10 flex flex-col items-center text-center">
{isVikasBirthday && (
<div className="w-16 h-16 rounded-full border border-zinc-800 overflow-hidden mb-4 relative">
<img src={USER_IMAGE} alt="Profile" className="w-full h-full object-cover grayscale opacity-90" />
</div>
)}
<h3 className={cn("text-5xl font-light tracking-tighter tabular-nums leading-none mb-1", isVikasBirthday ? "text-white" : "text-foreground")}>
{selectedDate.getDate()}
</h3>
<p className={cn("text-xs font-medium uppercase tracking-widest", isVikasBirthday ? "text-zinc-500" : "text-muted-foreground")}>
{MONTHS[selectedDate.getMonth()]} {selectedDate.getFullYear()}
</p>
<div className="mt-6 w-full space-y-4">
<p className={cn("text-sm font-medium", isVikasBirthday ? "text-zinc-300" : "text-foreground")}>
{isVikasBirthday ? "Special Event" : "Date Selected"}
</p>
{isVikasBirthday && (
<div className="flex items-center justify-center gap-1.5 text-zinc-500">
<MapPin size={14} />
<span className="text-xs font-medium tracking-wide">Dehradun, India</span>
</div>
)}
<button
onClick={() => setIsPopupOpen(false)}
className={cn(
"w-full py-3 rounded-xl text-xs font-medium transition-colors",
isVikasBirthday ? "bg-zinc-100 text-zinc-900 hover:bg-white" : "bg-foreground/5 text-foreground hover:bg-foreground/10"
)}
>
Close
</button>
</div>
</div>
</motion.div>
</div>
)}
</AnimatePresence>
</div>
)
}
Dependencies
framer-motion: latestlucide-react: latestcanvas-confetti: latestdate-fns: latest
Props
Component property reference.
| Name | Type | Default | Description |
|---|---|---|---|
| className | string | — | Adds extra utility classes to the calendar shell. |
| initialDate | Date | new Date(2002, 4, 1) | Sets the initially visible month and year. |
| onDateSelect | (date: Date) => void | — | Fires when a user selects a day from the grid. |
Most components here are inspired by outstanding libraries and creators in the ecosystem. I don’t claim to be the original author — this is my space for learning, rebuilding, and understanding great work at a deeper level.
I’m still a student of the craft, constantly studying the best and translating what I learn through my own perspective. Every piece reflects curiosity, respect for the community, and small creative touches that feel true to me.
I’ve done my best to credit inspirations properly. If anything is missing or inaccurate, I truly appreciate a message so it can be corrected with care.