Modern neon-styled calendar component with month navigation, date selection, event indicators, and selected date details panel. Features glowing accent colors, smooth hover states, event dots on specific dates, and today indicator ring. Built with shadcn/ui Card and Button components with custom neon CSS variables for consistent theming. Perfect for scheduling apps, event planners, booking systems, and any application requiring an elegant date selection interface with visual event markers.
Files
"use client"
import { useState } from "react"
import {
addMonths,
eachDayOfInterval,
endOfMonth,
endOfWeek,
format,
isSameDay,
isSameMonth,
isToday,
startOfMonth,
startOfWeek,
subMonths,
} from "date-fns"
import { Calendar, ChevronLeft, ChevronRight } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
const WEEKDAY_NAMES = ["MO", "TU", "WE", "TH", "FR", "SA", "SU"]
const EVENTS: Record<string, string> = {
"2025-01-07": "Team meeting at 10:00 AM",
"2025-01-17": "Today is a good day, like every other day! Enjoy your time.",
"2025-01-31": "Project deadline - Submit final reports",
"2025-02-14": "Valentine's Day celebration",
"2025-02-20": "Product launch event",
"2025-10-26": "Current day - you're viewing this calendar!",
}
function generateCalendarDays(currentMonth: Date) {
const monthStart = startOfMonth(currentMonth)
const monthEnd = endOfMonth(currentMonth)
const calendarStart = startOfWeek(monthStart, { weekStartsOn: 1 }) // Monday
const calendarEnd = endOfWeek(monthEnd, { weekStartsOn: 1 })
const days = eachDayOfInterval({
start: calendarStart,
end: calendarEnd,
})
return days.map((date) => {
const dateStr = format(date, "yyyy-MM-dd")
return {
date,
num: date.getDate(),
outside: !isSameMonth(date, currentMonth),
event: dateStr in EVENTS,
}
})
}
export default function NeonCalendar() {
const today = new Date()
const [currentDate, setCurrentDate] = useState(today)
const [selectedDate, setSelectedDate] = useState(today)
const days = generateCalendarDays(currentDate)
const selectedStr = format(selectedDate, "yyyy-MM-dd")
const handlePrevMonth = () => {
setCurrentDate(subMonths(currentDate, 1))
}
const handleNextMonth = () => {
setCurrentDate(addMonths(currentDate, 1))
}
const handleDateSelect = (date: Date) => {
setSelectedDate(date)
}
const selectedEvent =
EVENTS[selectedStr] ||
"No events scheduled for this day. Enjoy your free time!"
return (
<Card
className="bg-card mx-auto w-full max-w-md gap-0 rounded-3xl border p-4 shadow-[0_20px_60px_rgba(0,0,0,0.08)] dark:shadow-[0_20px_60px_rgba(0,0,0,0.45)]"
role="group"
aria-label="Calendar"
>
<CardHeader className="px-4 pt-3 pb-2">
<div className="bg-muted/50 flex items-center justify-between rounded-2xl border px-4 py-3">
<Button
variant="ghost"
size="icon"
className="hover:bg-accent/10 h-9 w-9 rounded-full"
aria-label="Previous month"
onClick={handlePrevMonth}
>
<ChevronLeft className="h-5 w-5" />
</Button>
<CardTitle className="text-xl font-semibold tracking-wide">
{format(currentDate, "MMMM, yyyy")}
</CardTitle>
<Button
variant="ghost"
size="icon"
className="hover:bg-accent/10 h-9 w-9 rounded-full"
aria-label="Next month"
onClick={handleNextMonth}
>
<ChevronRight className="h-5 w-5" />
</Button>
</div>
</CardHeader>
<CardContent className="px-4 pt-2 pb-4">
<div
className="bg-muted/50 rounded-2xl border p-5"
role="grid"
aria-label="Month grid"
>
<div className="mb-3 grid grid-cols-7 gap-y-3" role="row">
{WEEKDAY_NAMES.map((day) => (
<span
key={day}
className="text-muted-foreground text-center text-xs tracking-wide"
>
{day}
</span>
))}
</div>
<div className="grid grid-cols-7 gap-y-3" role="rowgroup">
{days.map((day, idx) => {
const isSelected = isSameDay(day.date, selectedDate)
const hasEvent = day.event
const isTodayDate = isToday(day.date)
return (
<button
key={idx}
role="gridcell"
aria-label={
isSelected
? `Selected date, ${day.num}`
: isTodayDate
? `Today, ${day.num}`
: `${day.num}`
}
onClick={() => handleDateSelect(day.date)}
className={`relative mx-auto grid h-9 w-9 place-items-center rounded-full text-[15px] font-medium transition-colors ${day.outside ? "opacity-40" : ""} ${isTodayDate ? "ring-2 ring-[hsl(var(--neon-accent))]/20" : ""} ${
isSelected
? "bg-[hsl(var(--neon-accent))] text-[hsl(var(--neon-selected-text))] shadow-[0_2px_10px_hsl(var(--neon-accent)/.5)]"
: "hover:bg-accent/10"
} `}
>
{day.num}
{hasEvent && (
<span
className="absolute right-1 bottom-1 h-[5px] w-[5px] rounded-full bg-[hsl(var(--neon-accent-dot))]"
aria-hidden="true"
/>
)}
</button>
)
})}
</div>
</div>
<div className="bg-muted/50 mt-5 rounded-2xl border p-5">
<div className="flex items-center gap-2 font-semibold text-[hsl(var(--neon-accent))]">
<Calendar className="h-4 w-4" />
<span>{format(selectedDate, "d MMMM, yyyy")}</span>
</div>
<p
id="calendar-note"
className="text-muted-foreground mt-2 text-[15px] leading-relaxed"
>
{selectedEvent}
</p>
</div>
</CardContent>
</Card>
)
}
Neon-styled calendar with event dots and date selection
neon-calendar-01
March, 2026
MOTUWETHFRSASU
29 March, 2026
No events scheduled for this day. Enjoy your free time!
"use client"
import { useState } from "react"
import {
addMonths,
eachDayOfInterval,
endOfMonth,
endOfWeek,
format,
isSameDay,
isSameMonth,
isToday,
startOfMonth,
startOfWeek,
subMonths,
} from "date-fns"
import { Calendar, ChevronLeft, ChevronRight } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
const WEEKDAY_NAMES = ["MO", "TU", "WE", "TH", "FR", "SA", "SU"]
const EVENTS: Record<string, string> = {
"2025-01-07": "Team meeting at 10:00 AM",
"2025-01-17": "Today is a good day, like every other day! Enjoy your time.",
"2025-01-31": "Project deadline - Submit final reports",
"2025-02-14": "Valentine's Day celebration",
"2025-02-20": "Product launch event",
"2025-10-26": "Current day - you're viewing this calendar!",
}
function generateCalendarDays(currentMonth: Date) {
const monthStart = startOfMonth(currentMonth)
const monthEnd = endOfMonth(currentMonth)
const calendarStart = startOfWeek(monthStart, { weekStartsOn: 1 }) // Monday
const calendarEnd = endOfWeek(monthEnd, { weekStartsOn: 1 })
const days = eachDayOfInterval({
start: calendarStart,
end: calendarEnd,
})
return days.map((date) => {
const dateStr = format(date, "yyyy-MM-dd")
return {
date,
num: date.getDate(),
outside: !isSameMonth(date, currentMonth),
event: dateStr in EVENTS,
}
})
}
export default function NeonCalendar() {
const today = new Date()
const [currentDate, setCurrentDate] = useState(today)
const [selectedDate, setSelectedDate] = useState(today)
const days = generateCalendarDays(currentDate)
const selectedStr = format(selectedDate, "yyyy-MM-dd")
const handlePrevMonth = () => {
setCurrentDate(subMonths(currentDate, 1))
}
const handleNextMonth = () => {
setCurrentDate(addMonths(currentDate, 1))
}
const handleDateSelect = (date: Date) => {
setSelectedDate(date)
}
const selectedEvent =
EVENTS[selectedStr] ||
"No events scheduled for this day. Enjoy your free time!"
return (
<Card
className="bg-card mx-auto w-full max-w-md gap-0 rounded-3xl border p-4 shadow-[0_20px_60px_rgba(0,0,0,0.08)] dark:shadow-[0_20px_60px_rgba(0,0,0,0.45)]"
role="group"
aria-label="Calendar"
>
<CardHeader className="px-4 pt-3 pb-2">
<div className="bg-muted/50 flex items-center justify-between rounded-2xl border px-4 py-3">
<Button
variant="ghost"
size="icon"
className="hover:bg-accent/10 h-9 w-9 rounded-full"
aria-label="Previous month"
onClick={handlePrevMonth}
>
<ChevronLeft className="h-5 w-5" />
</Button>
<CardTitle className="text-xl font-semibold tracking-wide">
{format(currentDate, "MMMM, yyyy")}
</CardTitle>
<Button
variant="ghost"
size="icon"
className="hover:bg-accent/10 h-9 w-9 rounded-full"
aria-label="Next month"
onClick={handleNextMonth}
>
<ChevronRight className="h-5 w-5" />
</Button>
</div>
</CardHeader>
<CardContent className="px-4 pt-2 pb-4">
<div
className="bg-muted/50 rounded-2xl border p-5"
role="grid"
aria-label="Month grid"
>
<div className="mb-3 grid grid-cols-7 gap-y-3" role="row">
{WEEKDAY_NAMES.map((day) => (
<span
key={day}
className="text-muted-foreground text-center text-xs tracking-wide"
>
{day}
</span>
))}
</div>
<div className="grid grid-cols-7 gap-y-3" role="rowgroup">
{days.map((day, idx) => {
const isSelected = isSameDay(day.date, selectedDate)
const hasEvent = day.event
const isTodayDate = isToday(day.date)
return (
<button
key={idx}
role="gridcell"
aria-label={
isSelected
? `Selected date, ${day.num}`
: isTodayDate
? `Today, ${day.num}`
: `${day.num}`
}
onClick={() => handleDateSelect(day.date)}
className={`relative mx-auto grid h-9 w-9 place-items-center rounded-full text-[15px] font-medium transition-colors ${day.outside ? "opacity-40" : ""} ${isTodayDate ? "ring-2 ring-[hsl(var(--neon-accent))]/20" : ""} ${
isSelected
? "bg-[hsl(var(--neon-accent))] text-[hsl(var(--neon-selected-text))] shadow-[0_2px_10px_hsl(var(--neon-accent)/.5)]"
: "hover:bg-accent/10"
} `}
>
{day.num}
{hasEvent && (
<span
className="absolute right-1 bottom-1 h-[5px] w-[5px] rounded-full bg-[hsl(var(--neon-accent-dot))]"
aria-hidden="true"
/>
)}
</button>
)
})}
</div>
</div>
<div className="bg-muted/50 mt-5 rounded-2xl border p-5">
<div className="flex items-center gap-2 font-semibold text-[hsl(var(--neon-accent))]">
<Calendar className="h-4 w-4" />
<span>{format(selectedDate, "d MMMM, yyyy")}</span>
</div>
<p
id="calendar-note"
className="text-muted-foreground mt-2 text-[15px] leading-relaxed"
>
{selectedEvent}
</p>
</div>
</CardContent>
</Card>
)
}