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 WithEventListDemo

Key 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: