- Accordion
- Alert
- Avatar
- Badge
- Breadcrumb
- Button
- Button Group
- Calendarnew
- Card
- Carousel
- Checkbox
- Chip
- Collapse
- Dialog
- Drawer
- Footer
- Gallery
- Icon Button
- Image
- Input
- List
- Mapnew
- Menu
- Navbar
- Pagination
- Popover
- Progress
- Radio
- Rating
- Select
- Sidebar
- Skeletonnew
- Slider
- Software Purchase Card
- Sonner / Toastnew
- Speed Dial
- Spinner
- Stepper
- Switch
- Table
- Tabs
- Textarea
- Timeline
- Tooltip
- Typography
- Video
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
"use client"
import * as React from "react"
import { Calendar } from "@/components/ui/calendar"
export function CalendarDemo() {
const [date, setDate] = React.useState<Date | undefined>(new Date())
return (
<Calendar
mode="single"
selected={date}
onSelect={setDate}
className="rounded-md border"
/>
)
}
Installation
pnpm dlx @creative-tim/ui@latest add calendar
Usage
import CalendarDemo from "@/components/calendar-demo"<CalendarDemo />Examples
Booking Calendar
A calendar configured with mode='range' displaying random prices for each date, perfect for booking systems, vacation rentals, and accommodation services where pricing varies by date.
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
"use client"
import * as React from "react"
import {
DayButton,
getDefaultClassNames,
MonthGrid,
type DateRange,
} from "react-day-picker"
import { cn } from "@/lib/utils"
import { Calendar } from "@/components/ui/calendar"
// Generate random prices for dates
const generatePrice = (date: Date): number => {
const seed = date.getTime()
return Math.floor((seed % 100) + 30) // Prices between $30-$129
}
const CustomDayButton = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<typeof DayButton>
>(({ className, day, modifiers, ...props }, ref) => {
const defaultClassNames = getDefaultClassNames()
const price = generatePrice(day.date)
return (
<button
ref={ref}
data-selected-single={
modifiers.selected &&
!modifiers.range_start &&
!modifiers.range_end &&
!modifiers.range_middle
}
data-range-start={modifiers.range_start}
data-range-end={modifiers.range_end}
data-range-middle={modifiers.range_middle}
data-today={modifiers.today}
data-disabled={modifiers.disabled}
className={cn(
"data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground hover:bg-accent hover:text-accent-foreground focus-visible:ring-ring flex aspect-square size-auto w-full min-w-[var(--cell-size)] cursor-pointer flex-col items-center justify-between py-2 text-sm font-normal transition-colors focus-visible:ring-2 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50",
modifiers.today &&
"bg-accent text-accent-foreground data-[selected=true]:bg-primary data-[selected=true]:text-primary-foreground",
"data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md",
defaultClassNames.day,
className
)}
{...props}
>
<span className="text-sm leading-none">{day.date.getDate()}</span>
<span className="text-muted-foreground data-[selected-single=true]:text-primary-foreground data-[range-start=true]:text-primary-foreground data-[range-end=true]:text-primary-foreground pt-1 text-[10px] leading-none font-medium">
${price}
</span>
</button>
)
})
CustomDayButton.displayName = "CustomDayButton"
export function CalendarBooking() {
const [date, setDate] = React.useState<DateRange | undefined>({
from: undefined,
to: undefined,
})
return (
<div className="space-y-4">
<div className="text-muted-foreground text-sm">
{date?.from && date?.to
? `Selected: ${date.from.toLocaleDateString()} - ${date.to.toLocaleDateString()}`
: date?.from
? `Select check-out date`
: "Select check-in date"}
</div>
<Calendar
mode="range"
selected={date}
onSelect={setDate}
numberOfMonths={2}
className="rounded-md border [--cell-size:--spacing(11)]"
components={{
DayButton: (props) => <CustomDayButton {...props} />,
}}
/>
</div>
)
}
Calendar With Popover
A calendar integrated within a popover component, providing a compact date picker that appears on demand. Perfect for forms and input fields where space is limited.
"use client"
import * as React from "react"
import { format } from "date-fns"
import { Calendar as CalendarIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Calendar } from "@/components/ui/calendar"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
export function CalendarWithPopover() {
const [date, setDate] = React.useState<Date>()
return (
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
className="w-[280px] justify-start text-left font-normal"
>
<CalendarIcon className="mr-2 h-4 w-4" />
{date ? format(date, "PPP") : <span>Pick a date</span>}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
selected={date}
onSelect={setDate}
initialFocus
/>
</PopoverContent>
</Popover>
)
}
Calendar Multiple Months
A calendar displaying multiple months simultaneously, allowing users to view and select dates across different months without navigation. Ideal for planning and scheduling interfaces.
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
"use client"
import * as React from "react"
import { Calendar } from "@/components/ui/calendar"
export function CalendarMultipleMonths() {
const [date, setDate] = React.useState<Date | undefined>(new Date())
return (
<Calendar
mode="single"
selected={date}
onSelect={setDate}
numberOfMonths={3}
className="rounded-md border"
/>
)
}
Calendar With Events
A calendar component that displays events with colored dots on specific dates. Clicking on a date with events opens a modal showing event details. Includes an "Add Events" button for managing events. Perfect for event calendars, scheduling systems, and activity planners.
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
"use client"
import * as React from "react"
import { format } from "date-fns"
import { DayButton, getDefaultClassNames } from "react-day-picker"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Calendar } from "@/components/ui/calendar"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
// Generate events for current month dynamically
const generateEventsForCurrentMonth = (): Record<
string,
Array<{ title: string; time: string; description: string; type?: string }>
> => {
const now = new Date()
const currentYear = now.getFullYear()
const currentMonth = now.getMonth()
const daysInMonth = new Date(currentYear, currentMonth + 1, 0).getDate()
// Generate events for some days in the current month
const eventDays = [5, 8, 12, 15, 18, 20, 22, 25, 28]
const events: Record<
string,
Array<{ title: string; time: string; description: string; type?: string }>
> = {}
eventDays.forEach((day) => {
if (day <= daysInMonth) {
const dateKey = format(
new Date(currentYear, currentMonth, day),
"yyyy-MM-dd"
)
const dayEvents: Array<{
title: string
time: string
description: string
type?: string
}> = []
// Add different events based on day
if (day === 5) {
dayEvents.push({
title: "Project Kickoff",
time: "9:00 AM",
description: "Starting a new project with the development team",
type: "meeting",
})
} else if (day === 8) {
dayEvents.push(
{
title: "Design Review",
time: "2:00 PM",
description: "Reviewing the latest design mockups",
type: "review",
},
{
title: "Client Call",
time: "4:00 PM",
description: "Weekly sync with key clients",
type: "call",
}
)
} else if (day === 12) {
dayEvents.push({
title: "Sprint Planning",
time: "10:00 AM",
description: "Planning the next sprint tasks",
type: "planning",
})
} else if (day === 15) {
dayEvents.push(
{
title: "Team Meeting",
time: "10:00 AM",
description: "Quarterly planning session with the team",
type: "meeting",
},
{
title: "Client Presentation",
time: "2:00 PM",
description: "Presenting the new product features to key clients",
type: "presentation",
}
)
} else if (day === 18) {
dayEvents.push({
title: "Code Review",
time: "11:00 AM",
description: "Reviewing pull requests from the team",
type: "review",
})
} else if (day === 20) {
dayEvents.push(
{
title: "Workshop",
time: "9:00 AM",
description: "Design system workshop for the development team",
type: "workshop",
},
{
title: "Team Lunch",
time: "12:30 PM",
description: "Monthly team lunch gathering",
type: "social",
}
)
} else if (day === 22) {
dayEvents.push({
title: "Demo Day",
time: "3:00 PM",
description: "Showcasing new features to stakeholders",
type: "demo",
})
} else if (day === 25) {
dayEvents.push(
{
title: "Product Launch",
time: "11:00 AM",
description: "Launch event for the new product line",
type: "launch",
},
{
title: "Networking Event",
time: "6:00 PM",
description: "Industry networking event at the convention center",
type: "networking",
}
)
} else if (day === 28) {
dayEvents.push({
title: "Retrospective",
time: "2:00 PM",
description: "Sprint retrospective meeting",
type: "meeting",
})
}
if (dayEvents.length > 0) {
events[dateKey] = dayEvents
}
}
})
return events
}
const TIME_OPTIONS = [
"12:00 AM",
"12:30 AM",
"01:00 AM",
"01:30 AM",
"02:00 AM",
"02:30 AM",
"03:00 AM",
"03:30 AM",
"04:00 AM",
"04:30 AM",
"05:00 AM",
"05:30 AM",
"06:00 AM",
"06:30 AM",
"07:00 AM",
"07:30 AM",
"08:00 AM",
"08:30 AM",
"09:00 AM",
"09:30 AM",
"10:00 AM",
"10:30 AM",
"11:00 AM",
"11:30 AM",
"12:00 PM",
"12:30 PM",
"01:00 PM",
"01:30 PM",
"02:00 PM",
"02:30 PM",
"03:00 PM",
"03:30 PM",
"04:00 PM",
"04:30 PM",
"05:00 PM",
"05:30 PM",
"06:00 PM",
"06:30 PM",
"07:00 PM",
"07:30 PM",
"08:00 PM",
"08:30 PM",
"09:00 PM",
"09:30 PM",
"10:00 PM",
"10:30 PM",
"11:00 PM",
"11:30 PM",
]
const getEventColor = (type?: string): string => {
switch (type) {
case "meeting":
return "bg-blue-500"
case "review":
return "bg-purple-500"
case "presentation":
case "demo":
return "bg-green-500"
case "workshop":
case "launch":
return "bg-orange-500"
case "social":
case "networking":
return "bg-pink-500"
case "call":
return "bg-cyan-500"
case "planning":
return "bg-indigo-500"
default:
return "bg-gray-500"
}
}
const CustomDayButton = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<typeof DayButton> & {
eventsData?: Record<
string,
Array<{ title: string; time: string; description: string; type?: string }>
>
onEventClick?: (date: Date) => void
}
>(({ className, day, modifiers, eventsData, onEventClick, ...props }, ref) => {
const defaultClassNames = getDefaultClassNames()
const dateKey = format(day.date, "yyyy-MM-dd")
const dayEvents = (eventsData || {})[dateKey] || []
const hasEvents = dayEvents.length > 0
const handleEventClick = (e: React.MouseEvent, clickedDate: Date) => {
e.stopPropagation()
if (onEventClick) {
onEventClick(clickedDate)
}
}
return (
<div className="relative flex min-h-[56px] w-full flex-col items-center gap-1.5">
<button
ref={ref}
data-selected-single={
modifiers.selected &&
!modifiers.range_start &&
!modifiers.range_end &&
!modifiers.range_middle
}
data-range-start={modifiers.range_start}
data-range-end={modifiers.range_end}
data-range-middle={modifiers.range_middle}
data-today={modifiers.today}
data-disabled={modifiers.disabled}
className={cn(
"data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground hover:bg-accent hover:text-accent-foreground focus-visible:ring-ring flex aspect-square size-auto h-12 w-full min-w-[48px] cursor-pointer flex-col items-center justify-center rounded-md text-sm font-normal transition-colors focus-visible:ring-2 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50",
modifiers.today &&
"bg-accent text-accent-foreground data-[selected=true]:bg-primary data-[selected=true]:text-primary-foreground",
defaultClassNames.day,
className
)}
{...props}
>
{day.date.getDate()}
</button>
{hasEvents && (
<div className="absolute bottom-3 flex max-w-full flex-wrap justify-center gap-0.5">
{dayEvents.slice(0, 2).map((event, index) => (
<div
key={index}
onClick={(e) => handleEventClick(e, day.date)}
className={cn(
"flex h-2 w-2 cursor-pointer items-center justify-center rounded-md px-1.5 text-[10px] leading-tight text-white transition-opacity hover:opacity-80",
getEventColor(event.type)
)}
>
{/* {event.type || "Event"} */}
</div>
))}
{dayEvents.length > 2 && (
<div
onClick={(e) => handleEventClick(e, day.date)}
className="flex h-2 cursor-pointer items-center justify-center rounded-md bg-gray-500 px-1.5 text-[10px] leading-tight text-white transition-opacity hover:opacity-80"
>
+{dayEvents.length - 2}
</div>
)}
</div>
)}
</div>
)
})
CustomDayButton.displayName = "CustomDayButton"
export function CalendarWithEvents() {
const [currentMonth, setCurrentMonth] = React.useState<Date>(new Date())
const [date, setDate] = React.useState<Date | undefined>(new Date())
const [selectedDate, setSelectedDate] = React.useState<Date | null>(null)
const [isDialogOpen, setIsDialogOpen] = React.useState(false)
const [isAddingEvent, setIsAddingEvent] = React.useState(false)
const [eventName, setEventName] = React.useState("")
const [eventType, setEventType] = React.useState("")
const [eventHour, setEventHour] = React.useState("")
const [eventsState, setEventsState] = React.useState(
generateEventsForCurrentMonth()
)
const handleDateSelect = (selectedDate: Date | undefined) => {
if (selectedDate) {
setDate(selectedDate)
setSelectedDate(selectedDate)
const dateKey = format(selectedDate, "yyyy-MM-dd")
const dayEvents = eventsState[dateKey] || []
setIsAddingEvent(false)
// Open dialog if there are events
if (dayEvents.length > 0) {
setIsDialogOpen(true)
}
}
}
const handleEventClick = (clickedDate: Date) => {
setDate(clickedDate)
setSelectedDate(clickedDate)
setIsAddingEvent(false)
setIsDialogOpen(true)
}
const selectedDateKey = selectedDate
? format(selectedDate, "yyyy-MM-dd")
: date
? format(date, "yyyy-MM-dd")
: null
const selectedDateEvents = selectedDateKey
? eventsState[selectedDateKey] || []
: []
const handleAddEventsClick = () => {
// If no date is selected, use today's date
if (!selectedDate && date) {
setSelectedDate(date)
} else if (!selectedDate) {
setSelectedDate(new Date())
}
setIsAddingEvent(true)
setIsDialogOpen(true)
setEventName("")
setEventType("")
setEventHour("")
}
const handleSaveEvent = () => {
if (!selectedDateKey || !eventName || !eventType || !eventHour) {
return
}
const newEvent = {
title: eventName,
time: eventHour, // Already in 12-hour format from Select
description: `Event scheduled for ${eventType}`,
type: eventType,
}
setEventsState((prev) => ({
...prev,
[selectedDateKey]: [...(prev[selectedDateKey] || []), newEvent],
}))
setIsAddingEvent(false)
setEventName("")
setEventType("")
setEventHour("")
}
const handleDialogClose = (open: boolean) => {
setIsDialogOpen(open)
if (!open) {
setIsAddingEvent(false)
setEventName("")
setEventType("")
setEventHour("")
}
}
return (
<div className="space-y-4">
<div className="flex justify-end">
<Button onClick={handleAddEventsClick}>Add Events</Button>
</div>
<Calendar
mode="single"
selected={date}
onSelect={handleDateSelect}
month={currentMonth}
onMonthChange={setCurrentMonth}
defaultMonth={new Date()}
className="rounded-md border [--cell-size:48px]"
components={{
DayButton: (props) => (
<CustomDayButton
{...props}
eventsData={eventsState}
onEventClick={handleEventClick}
/>
),
}}
/>
<Dialog open={isDialogOpen} onOpenChange={handleDialogClose}>
<DialogContent className="sm:max-w-[500px]">
<DialogHeader>
<DialogTitle>
{selectedDate
? format(selectedDate, "EEEE, MMMM d, yyyy")
: date
? format(date, "EEEE, MMMM d, yyyy")
: "Events"}
</DialogTitle>
<DialogDescription>
{isAddingEvent
? "Add a new event for this date"
: selectedDateEvents.length > 0
? `${selectedDateEvents.length} event${selectedDateEvents.length > 1 ? "s" : ""} scheduled`
: "No events scheduled for this date"}
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
{isAddingEvent ? (
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="event-name">Name of Event</Label>
<Input
id="event-name"
placeholder="Enter event name"
value={eventName}
onChange={(e) => setEventName(e.target.value)}
/>
</div>
<div className="space-y-2">
<Label htmlFor="event-type">Type</Label>
<Select value={eventType} onValueChange={setEventType}>
<SelectTrigger id="event-type">
<SelectValue placeholder="Select event type" />
</SelectTrigger>
<SelectContent>
<SelectItem value="meeting">Meeting</SelectItem>
<SelectItem value="review">Review</SelectItem>
<SelectItem value="presentation">Presentation</SelectItem>
<SelectItem value="demo">Demo</SelectItem>
<SelectItem value="workshop">Workshop</SelectItem>
<SelectItem value="launch">Launch</SelectItem>
<SelectItem value="social">Social</SelectItem>
<SelectItem value="networking">Networking</SelectItem>
<SelectItem value="call">Call</SelectItem>
<SelectItem value="planning">Planning</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="event-hour">Hour</Label>
<Select value={eventHour} onValueChange={setEventHour}>
<SelectTrigger id="event-hour">
<SelectValue placeholder="Select time" />
</SelectTrigger>
<SelectContent>
{TIME_OPTIONS.map((time) => (
<SelectItem key={time} value={time}>
{time}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
) : selectedDateEvents.length > 0 ? (
<>
{selectedDateEvents.map((event, index) => (
<div key={index} className="space-y-2 rounded-lg border p-4">
<div className="flex items-center justify-between">
<h3 className="font-semibold">{event.title}</h3>
<span className="text-muted-foreground text-sm">
{event.time}
</span>
</div>
<p className="text-muted-foreground text-sm">
{event.description}
</p>
</div>
))}
<div className="pt-2">
<Button
variant="outline"
className="w-full"
onClick={() => {
setIsAddingEvent(true)
setEventName("")
setEventType("")
setEventHour("")
}}
>
Add Another Event
</Button>
</div>
</>
) : (
<p className="text-muted-foreground py-4 text-center text-sm">
No events scheduled. Click "Add Events" to create a new event.
</p>
)}
</div>
{isAddingEvent && (
<DialogFooter>
<Button
variant="outline"
onClick={() => {
setIsAddingEvent(false)
setEventName("")
setEventType("")
setEventHour("")
}}
>
Cancel
</Button>
<Button
onClick={handleSaveEvent}
disabled={!eventName || !eventType || !eventHour}
>
Save Event
</Button>
</DialogFooter>
)}
</DialogContent>
</Dialog>
</div>
)
}
Props
The calendar component is built using react-day-picker and accepts the following props:
Calendar (Root Component)
| Prop | Type | Default | Description |
|---|---|---|---|
mode | "single" | "range" | "multiple" | - | Determines the selection mode: single date, date range, or multiple dates |
selected | Date | Date[] | { from: Date, to: Date } | - | The currently selected date(s) or range |
onSelect | (date: Date | Date[] | { from: Date, to: Date } | undefined) => void | - | Event handler called when the selection changes |
numberOfMonths | number | 1 | Number of months to display side by side |
showOutsideDays | boolean | true | Whether to show days from previous/next month |
disabled | boolean | Date | Date[] | Matcher | false | Dates that cannot be selected |
captionLayout | "label" | "dropdown" | "dropdown-months" | "dropdown-years" | "label" | Layout of the month/year navigation |
buttonVariant | "default" | "destructive" | "outline" | "secondary" | "ghost" | "link" | "ghost" | Variant of the navigation buttons |
className | string | - | Additional CSS classes to apply to the calendar |
classNames | object | - | Custom class names for calendar elements |
initialFocus | boolean | false | Whether to focus the calendar on mount |
Best Practices
- Use single mode for simple date selection in forms
- Use range mode for booking systems, event scheduling, or date filtering
- Integrate with popover for space-efficient date pickers in forms
- Display multiple months when users need to compare dates across months
- Provide clear visual feedback for selected dates and disabled dates
- Ensure keyboard navigation works properly for accessibility
- Use appropriate date formatting with date-fns for display
- Consider timezone handling for applications with global users
The calendar component provides a flexible and accessible way to handle date selection in your application. It's ideal for forms, booking systems, scheduling interfaces, and any application where date input is required.