Date selection is one of the most common features in modern web applications. Whether you’re building a booking platform, event scheduler, admin dashboard, hotel reservation system, or appointment manager, a well-designed calendar improves the user experience.
In this guide, you’ll learn how to install the Shadcn Calendar component, add it manually if needed, and build practical calendar examples like appointment scheduling and event management.
Official Shadcn Calendar
This Shadcn Calendar component provides a flexible and customizable date picker built on top of react-day-picker. Since it’s built with React and Tailwind CSS, you can easily style it to match your application’s design.
Install using CLI
Use the following command to install the Calendar component:
pnpm dlx shadcn@latest add calendar
Manual installation
Install the required dependencies:
npm install react-day-picker date-fns
Next, make sure the Button component is available in your project because the Calendar component uses it for navigation controls.
Then create a calendar.tsx file and add the following code:
"use client"
import * as React from "react"
import {
ChevronDownIcon,
ChevronLeftIcon,
ChevronRightIcon,
} from "lucide-react"
import {
DayPicker,
getDefaultClassNames,
type DayButton,
type Locale,
} from "react-day-picker"
import { cn } from "@/lib/utils"
import { Button, buttonVariants } from "@/components/ui/button"
function Calendar({
className,
classNames,
showOutsideDays = true,
captionLayout = "label",
buttonVariant = "ghost",
locale,
formatters,
components,
...props
}: React.ComponentProps<typeof DayPicker> & {
buttonVariant?: React.ComponentProps<typeof Button>["variant"]
}) {
const defaultClassNames = getDefaultClassNames()
return (
<DayPicker
showOutsideDays={showOutsideDays}
className={cn(
"group/calendar bg-background p-2 [--cell-radius:var(--radius-md)] [--cell-size:--spacing(7)] in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent",
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
className
)}
captionLayout={captionLayout}
locale={locale}
formatters={{
formatMonthDropdown: (date) =>
date.toLocaleString(locale?.code, { month: "short" }),
...formatters,
}}
classNames={{
root: cn("w-fit", defaultClassNames.root),
months: cn(
"relative flex flex-col gap-4 md:flex-row",
defaultClassNames.months
),
month: cn("flex w-full flex-col gap-4", defaultClassNames.month),
nav: cn(
"absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1",
defaultClassNames.nav
),
button_previous: cn(
buttonVariants({ variant: buttonVariant }),
"size-(--cell-size) p-0 select-none aria-disabled:opacity-50",
defaultClassNames.button_previous
),
button_next: cn(
buttonVariants({ variant: buttonVariant }),
"size-(--cell-size) p-0 select-none aria-disabled:opacity-50",
defaultClassNames.button_next
),
month_caption: cn(
"flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)",
defaultClassNames.month_caption
),
dropdowns: cn(
"flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-medium",
defaultClassNames.dropdowns
),
dropdown_root: cn(
"cn-calendar-dropdown-root relative rounded-(--cell-radius)",
defaultClassNames.dropdown_root
),
dropdown: cn(
"absolute inset-0 bg-popover opacity-0",
defaultClassNames.dropdown
),
caption_label: cn(
"font-medium select-none",
captionLayout === "label"
? "cn-calendar-caption text-sm"
: "cn-calendar-caption-label flex items-center gap-1 rounded-(--cell-radius) text-sm [&>svg]:size-3.5 [&>svg]:text-muted-foreground",
defaultClassNames.caption_label
),
month_grid: cn("w-full border-collapse", defaultClassNames.month_grid),
weekdays: cn("flex", defaultClassNames.weekdays),
weekday: cn(
"flex-1 rounded-(--cell-radius) text-[0.8rem] font-normal text-muted-foreground select-none",
defaultClassNames.weekday
),
week: cn("mt-2 flex w-full", defaultClassNames.week),
week_number_header: cn(
"w-(--cell-size) select-none",
defaultClassNames.week_number_header
),
week_number: cn(
"text-[0.8rem] text-muted-foreground select-none",
defaultClassNames.week_number
),
day: cn(
"group/day relative aspect-square h-full w-full rounded-(--cell-radius) p-0 text-center select-none [&:last-child[data-selected=true]_button]:rounded-r-(--cell-radius)",
props.showWeekNumber
? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-(--cell-radius)"
: "[&:first-child[data-selected=true]_button]:rounded-l-(--cell-radius)",
defaultClassNames.day
),
range_start: cn(
"relative isolate z-0 rounded-l-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:right-0 after:w-4 after:bg-muted",
defaultClassNames.range_start
),
range_middle: cn("rounded-none", defaultClassNames.range_middle),
range_end: cn(
"relative isolate z-0 rounded-r-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:left-0 after:w-4 after:bg-muted",
defaultClassNames.range_end
),
today: cn(
"rounded-(--cell-radius) bg-muted text-foreground data-[selected=true]:rounded-none",
defaultClassNames.today
),
outside: cn(
"text-muted-foreground aria-selected:text-muted-foreground",
defaultClassNames.outside
),
disabled: cn(
"text-muted-foreground opacity-50",
defaultClassNames.disabled
),
hidden: cn("invisible", defaultClassNames.hidden),
...classNames,
}}
components={{
Root: ({ className, rootRef, ...props }) => {
return (
<div
data-slot="calendar"
ref={rootRef}
className={cn(className)}
{...props}
/>
)
},
Chevron: ({ className, orientation, ...props }) => {
if (orientation === "left") {
return (
<ChevronLeftIcon
className={cn("cn-rtl-flip size-4", className)}
{...props}
/>
)
}
if (orientation === "right") {
return (
<ChevronRightIcon
className={cn("cn-rtl-flip size-4", className)}
{...props}
/>
)
}
return (
<ChevronDownIcon className={cn("size-4", className)} {...props} />
)
},
DayButton: ({ ...props }) => (
<CalendarDayButton locale={locale} {...props} />
),
WeekNumber: ({ children, ...props }) => {
return (
<td {...props}>
<div className="flex size-(--cell-size) items-center justify-center text-center">
{children}
</div>
</td>
)
},
...components,
}}
{...props}
/>
)
}
function CalendarDayButton({
className,
day,
modifiers,
locale,
...props
}: React.ComponentProps<typeof DayButton> & { locale?: Partial<Locale> }) {
const defaultClassNames = getDefaultClassNames()
const ref = React.useRef<HTMLButtonElement>(null)
React.useEffect(() => {
if (modifiers.focused) ref.current?.focus()
}, [modifiers.focused])
return (
<Button
variant="ghost"
size="icon"
data-day={day.date.toLocaleDateString(locale?.code)}
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}
className={cn(
"relative isolate z-10 flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 border-0 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-[3px] group-data-[focused=true]/day:ring-ring/50 data-[range-end=true]:rounded-(--cell-radius) data-[range-end=true]:rounded-r-(--cell-radius) data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground data-[range-middle=true]:rounded-none data-[range-middle=true]:bg-muted data-[range-middle=true]:text-foreground data-[range-start=true]:rounded-(--cell-radius) data-[range-start=true]:rounded-l-(--cell-radius) data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground dark:hover:text-foreground [&>span]:text-xs [&>span]:opacity-70",
defaultClassNames.day,
className
)}
{...props}
/>
)
}
export { Calendar, CalendarDayButton }
Why use this Calendar component
Fully customizable with Tailwind CSS to match your application’s design.
Built on top of react-day-picker, providing a reliable and accessible calendar experience.
Supports single date, multiple date, and date range selection out of the box.
Perfect for booking systems, event management, dashboards, scheduling apps, and reservation platforms.
Shadcn Date & Time Calendar
Sometimes selecting only a date isn’t enough. Users often need to choose an available time as well.
This calendar combines date selection with available time slots, allowing users to book appointments from a single interface. It’s an excellent choice for applications where scheduling is an important part of the workflow.
Install using CLI
npx shadcn@latest add @shadcn-space/calendar-03
Install manually
Add the following component to your project.
"use client";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import { ScrollArea } from "@/components/ui/scroll-area";
export const title = "Calendar as Appointment Picker";
const CalendarThree = () => {
const [date, setDate] = useState<Date | undefined>(new Date());
const [selectedTime, setSelectedTime] = useState<string | null>(null);
const availableTimes = [
"09:00 AM",
"09:30 AM",
"10:00 AM",
"10:30 AM",
"11:00 AM",
"11:30 AM",
"01:00 PM",
"01:30 PM",
"02:00 PM",
"02:30 PM",
"03:00 PM",
"03:30 PM",
"04:00 PM",
"04:30 PM",
];
return (
<div className="flex items-center justify-center px-4">
<div className="flex divide-x overflow-hidden rounded-md border bg-background">
<Calendar mode="single" onSelect={setDate} selected={date} />
<div className="relative w-[249px] overflow-hidden">
<div className="absolute inset-0 grid gap-4">
<div className="space-y-2 px-4 pt-4">
<p className="text-center text-sm font-medium">Available Times</p>
</div>
<ScrollArea className="h-full overflow-y-auto">
<div className="grid grid-cols-1 gap-2 px-4 pb-4">
{availableTimes.map((time) => (
<Button
key={time}
onClick={() => setSelectedTime(time)}
size="sm"
variant={selectedTime === time ? "default" : "outline"}
>
{time}
</Button>
))}
</div>
</ScrollArea>
</div>
</div>
</div>
</div>
);
};
export default CalendarThree;
Key features
Date and time slot selection
Appointment scheduling
Availability management
Booking-friendly interface
Fully responsive layout
Best for: Healthcare platforms, salons, coaching applications, consultation booking, reservations, and service scheduling.
Calendar with Event List
Managing events becomes much easier when users can view their schedule alongside the calendar.
This component displays a monthly calendar together with a list of events for the selected date. Users can quickly see meetings, reminders, deadlines, or upcoming activities without switching between screens.
Install using CLI
npx shadcn@latest add @shadcn-space/calendar-08
Install manually
Add the following component to your project.
'use client'
import { useState } from 'react'
import { Button } from '@/components/ui/button'
import { Calendar } from '@/components/ui/calendar'
import { Card, CardContent, CardFooter } from '@/components/ui/card'
import { PlusIcon } from 'lucide-react'
const formatTimeRange = (from: Date, to: Date): string => {
const timeFmt = new Intl.DateTimeFormat('en-US', { hour: 'numeric', minute: '2-digit', hour12: true })
const dateFmt = new Intl.DateTimeFormat('en-US', { month: 'short', day: 'numeric' })
if (from.toDateString() === to.toDateString()) {
return `${timeFmt.format(from)} – ${timeFmt.format(to)}`
}
return `${dateFmt.format(from)} – ${dateFmt.format(to)}`
}
const colorMap = {
blue: { dot: 'bg-blue-500', bg: 'bg-blue-500/10', time: 'text-blue-500' },
teal: { dot: 'bg-teal-400', bg: 'bg-teal-400/10', time: 'text-teal-400' },
orange: { dot: 'bg-orange-400', bg: 'bg-orange-400/10', time: 'text-orange-400' }
} as const
const events = [
{ title: 'Product Standup', from: '2026-05-28T08:30:00', to: '2026-05-28T09:00:00', color: 'blue' as const },
{ title: 'UX Walkthrough', from: '2026-05-28T11:00:00', to: '2026-05-28T12:00:00', color: 'teal' as const },
{ title: 'Stakeholder Demo', from: '2026-05-28T15:00:00', to: '2026-05-28T16:30:00', color: 'orange' as const }
]
const WithEventListDemo = () => {
const [date, setDate] = useState<Date | undefined>(new Date())
return (
<>
<Card className='min-w-2xs pt-4'>
<CardContent className='px-4'>
<Calendar mode='single' selected={date} onSelect={setDate} className='w-full bg-transparent p-0' required />
</CardContent>
<CardFooter className='flex flex-col items-start gap-3 border-t bg-card'>
<div className='flex w-full items-center justify-between px-1'>
<div className='text-sm font-medium'>
{date?.toLocaleDateString('en-US', {
day: 'numeric',
month: 'long',
year: 'numeric'
})}
</div>
<Button variant='ghost' size='icon' className='size-6' title='Add Event'>
<PlusIcon />
<span className='sr-only'>Add Event</span>
</Button>
</div>
<div className='flex w-full flex-col gap-2'>
{events.map(event => {
const c = colorMap[event.color]
return (
<div
key={event.title}
className={`flex items-center gap-3 rounded-lg px-3 py-2 transition-opacity hover:opacity-80 ${c.bg}`}
>
<span className={`size-1.5 shrink-0 rounded-full ${c.dot}`} />
<span className='flex-1 truncate text-sm font-medium'>{event.title}</span>
<span className={`shrink-0 text-xs font-medium ${c.time}`}>
{formatTimeRange(new Date(event.from), new Date(event.to))}
</span>
</div>
)
})}
</div>
</CardFooter>
</Card>
</>
)
}
export default WithEventListDemoKey features
Monthly calendar with event list
Display meetings, reminders, and deadlines
Quick event overview
Easy event management
Responsive design
Best for: Team collaboration tools, project management software, productivity apps, internal dashboards, CRM systems, and planning applications.
Conclusion
The Shadcn Calendar component gives you a solid foundation for building calendars in React and Next.js applications. Since it’s fully customizable, you can start with a simple date picker and extend it into appointment schedulers, event planners, booking systems, or project calendars.
If you’re looking for ready-to-use calendar patterns, you can also explore advanced calendar components that include appointment booking, event lists, range selection, multiple months, and many other layouts to speed up development.
In the next article
In the next article, we’ll explore How to Use Shadcn Accordion Components in Your React & Next.js Projects.
Read Next
Continue learning more Shadcn components:
How to Use Shadcn Dialog Components in React & Next.js
How to Use Shadcn Select Components in React & Next.js
How to Use Shadcn Checkbox Components in React & Next.js
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.