Build websites 10x faster with HextaUI Blocks — Learn more
UI/UI

Calendar

A comprehensive calendar component for date selection with support for single dates, date ranges, and multiple date selection.

Jun 2025

S
M
T
W
T
F
S
function BasicCalendarExample() {
  const [date, setDate] = React.useState<Date>();

  return <Calendar selected={date} onSelect={setDate} />;
}

Installation

Install following dependencies:

npm install class-variance-authority lucide-react motion
pnpm add class-variance-authority lucide-react motion
yarn add class-variance-authority lucide-react motion
bun add class-variance-authority lucide-react motion

Copy and paste the following code into your project.

components/ui/calendar.tsx
"use client";

import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { motion, AnimatePresence } from "motion/react";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";

const calendarVariants = cva(
  "inline-block space-y-4 rounded-ele border border-border bg-background relative w-full max-w-sm mx-auto shadow-sm/2",
  {
    variants: {
      size: {
        sm: "p-2 sm:p-3 text-sm",
        default: "p-3 sm:p-4",
        lg: "p-4 sm:p-5 text-base",
      },
      alwaysOnTop: {
        true: "z-9999",
        false: "z-10",
      },
    },
    defaultVariants: {
      size: "default",
      alwaysOnTop: true,
    },
  }
);

const dayVariants = cva(
  "inline-flex h-8 w-8 sm:h-9 sm:w-9 items-center justify-center rounded-ele text-sm transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 cursor-pointer",
  {
    variants: {
      variant: {
        default:
          "text-foreground hover:bg-accent hover:text-accent-foreground focus-visible:ring-ring",
        selected:
          "bg-primary text-primary-foreground hover:bg-primary/90 focus-visible:ring-ring font-semibold",
        today:
          "bg-accent text-accent-foreground font-semibold hover:bg-accent/80 focus-visible:ring-ring",
        outside:
          "text-muted-foreground opacity-50 hover:bg-accent hover:text-accent-foreground focus-visible:ring-ring",
        disabled:
          "text-muted-foreground opacity-30 cursor-not-allowed",
        "range-start":
          "bg-primary text-primary-foreground rounded-r-none hover:bg-primary/90 focus-visible:ring-ring",
        "range-end":
          "bg-primary text-primary-foreground rounded-l-none hover:bg-primary/90 focus-visible:ring-ring",
        "range-middle":
          "bg-primary/20 text-foreground rounded-none hover:bg-primary/30 focus-visible:ring-ring",
      },
      size: {
        sm: "h-6 w-6 sm:h-7 sm:w-7 text-xs",
        default: "h-8 w-8 sm:h-9 sm:w-9 text-sm",
        lg: "h-9 w-9 sm:h-10 sm:w-10 text-base",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
);

interface CalendarProps extends VariantProps<typeof calendarVariants> {
  selected?: Date;
  onSelect?: (date: Date) => void;
  disabled?: (date: Date) => boolean;
  locale?: string;
  className?: string;
  showOutsideDays?: boolean;
  minDate?: Date;
  maxDate?: Date;
  mode?: "single" | "multiple" | "range";
  selectedDates?: Date[];
  selectedRange?: { from: Date; to?: Date };
  onSelectMultiple?: (dates: Date[]) => void;
  onSelectRange?: (range: { from: Date; to?: Date }) => void;
  showMonthYearPickers?: boolean;
  alwaysOnTop?: boolean;
}

const DAYS_OF_WEEK = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
const MONTHS = [
  "January",
  "February",
  "March",
  "April",
  "May",
  "June",
  "July",
  "August",
  "September",
  "October",
  "November",
  "December",
];

function Calendar({
  selected,
  onSelect,
  disabled,
  locale = "en-US",
  className,
  size,
  showOutsideDays = true,
  minDate,
  maxDate,
  mode = "single",
  selectedDates = [],
  selectedRange,
  onSelectMultiple,
  onSelectRange,
  showMonthYearPickers = false,
  alwaysOnTop = true,
  ...props
}: CalendarProps) {
  const [currentDate, setCurrentDate] = React.useState(selected || new Date());
  const [isAnimating, setIsAnimating] = React.useState(false);
  const [direction, setDirection] = React.useState<"left" | "right">("right");
  const today = new Date();
  const currentMonth = currentDate.getMonth();
  const currentYear = currentDate.getFullYear();

  // Generate year range for year picker (current year ± 50 years)
  const yearRange = Array.from({ length: 101 }, (_, i) => currentYear - 50 + i);

  // Get first day of the month and calculate calendar grid
  const firstDayOfMonth = new Date(currentYear, currentMonth, 1);
  const lastDayOfMonth = new Date(currentYear, currentMonth + 1, 0);
  const firstDayOfWeek = firstDayOfMonth.getDay();
  const daysInMonth = lastDayOfMonth.getDate();

  // Calculate previous month days to show
  const prevMonthLastDay = new Date(currentYear, currentMonth, 0).getDate();
  const prevMonthDays = Array.from(
    { length: firstDayOfWeek },
    (_, i) => prevMonthLastDay - firstDayOfWeek + i + 1
  );

  // Calculate next month days to show
  const totalCells = 42; // 6 rows × 7 days
  const currentMonthDays = Array.from({ length: daysInMonth }, (_, i) => i + 1);
  const remainingCells =
    totalCells - prevMonthDays.length - currentMonthDays.length;
  const nextMonthDays = Array.from({ length: remainingCells }, (_, i) => i + 1);
  const navigateMonth = (direction: "prev" | "next") => {
    setIsAnimating(true);
    setDirection(direction === "prev" ? "left" : "right");

    setTimeout(() => {
      const newDate = new Date(currentDate);
      if (direction === "prev") {
        newDate.setMonth(currentMonth - 1);
      } else {
        newDate.setMonth(currentMonth + 1);
      }
      setCurrentDate(newDate);
      setIsAnimating(false);
    }, 150);
  };

  const handleMonthChange = (month: string) => {
    const monthIndex = parseInt(month, 10);
    const newDate = new Date(currentDate);
    newDate.setMonth(monthIndex);
    setCurrentDate(newDate);
  };

  const handleYearChange = (year: string) => {
    const yearValue = parseInt(year, 10);
    const newDate = new Date(currentDate);
    newDate.setFullYear(yearValue);
    setCurrentDate(newDate);
  };

  const isDateDisabled = (date: Date) => {
    if (disabled?.(date)) return true;
    if (minDate && date < minDate) return true;
    if (maxDate && date > maxDate) return true;
    return false;
  };
  const isDateSelected = (date: Date) => {
    if (mode === "single") {
      return selected && isSameDay(date, selected);
    }
    if (mode === "multiple") {
      return selectedDates.some((d) => isSameDay(d, date));
    }
    if (mode === "range" && selectedRange) {
      if (!selectedRange.to) {
        // Only from date is selected
        return isSameDay(date, selectedRange.from);
      }
      const dateTime = date.getTime();
      const fromTime = selectedRange.from.getTime();
      const toTime = selectedRange.to.getTime();
      return dateTime >= fromTime && dateTime <= toTime;
    }
    return false;
  };

  const isDateInRange = (date: Date) => {
    if (mode === "range" && selectedRange) {
      if (!selectedRange.to) return isSameDay(date, selectedRange.from);
      const dateTime = date.getTime();
      const fromTime = selectedRange.from.getTime();
      const toTime = selectedRange.to.getTime();
      return dateTime > fromTime && dateTime < toTime;
    }
    return false;
  };

  const isRangeStart = (date: Date) => {
    if (mode === "range" && selectedRange) {
      return isSameDay(date, selectedRange.from);
    }
    return false;
  };

  const isRangeEnd = (date: Date) => {
    if (mode === "range" && selectedRange && selectedRange.to) {
      return isSameDay(date, selectedRange.to);
    }
    return false;
  };

  const isToday = (date: Date) => isSameDay(date, today);

  const handleDateClick = (day: number, monthOffset: number = 0) => {
    const clickedDate = new Date(currentYear, currentMonth + monthOffset, day);

    if (isDateDisabled(clickedDate)) return;

    if (mode === "single") {
      onSelect?.(clickedDate);
    } else if (mode === "multiple") {
      const newDates = selectedDates.some((d) => isSameDay(d, clickedDate))
        ? selectedDates.filter((d) => !isSameDay(d, clickedDate))
        : [...selectedDates, clickedDate];
      onSelectMultiple?.(newDates);
    } else if (mode === "range") {
      if (!selectedRange || (selectedRange.from && selectedRange.to)) {
        // Start new range selection - only set the 'from' date
        onSelectRange?.({ from: clickedDate });
      } else if (selectedRange.from && !selectedRange.to) {
        // Complete the range selection
        const from =
          selectedRange.from <= clickedDate ? selectedRange.from : clickedDate;
        const to =
          selectedRange.from <= clickedDate ? clickedDate : selectedRange.from;
        onSelectRange?.({ from, to });
      }
    }
  };
  const getDayVariant = (
    day: number,
    monthOffset: number = 0
  ):
    | "default"
    | "selected"
    | "today"
    | "outside"
    | "disabled"
    | "range-start"
    | "range-end"
    | "range-middle" => {
    const date = new Date(currentYear, currentMonth + monthOffset, day);

    if (isDateDisabled(date)) return "disabled";
    if (mode === "range" && selectedRange) {
      if (isRangeStart(date)) return "range-start";
      if (isRangeEnd(date)) return "range-end";
      if (isDateInRange(date)) return "range-middle";
    }
    if (isDateSelected(date)) return "selected";
    if (isToday(date)) return "today";
    if (monthOffset !== 0) return "outside";
    return "default";
  };

  const slideVariants = {
    enter: (direction: string) => ({
      x: direction === "right" ? 300 : -300,
      opacity: 0,
    }),
    center: {
      zIndex: 1,
      x: 0,
      opacity: 1,
    },
    exit: (direction: string) => ({
      zIndex: 0,
      x: direction === "right" ? -300 : 300,
      opacity: 0,
    }),
  };
  return (
    <div
      className={cn(calendarVariants({ size, alwaysOnTop }), className)}
      {...props}
    >
      {" "}
      {/* Header */}
      <div className="flex items-center justify-between">
        <button
          onClick={() => navigateMonth("prev")}
          className="inline-flex items-center justify-center rounded-ele p-1 sm:p-1.5 transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
          disabled={isAnimating}
        >
          <ChevronLeft className="h-4 w-4" />
        </button>

        <div className="flex items-center gap-1 sm:gap-2 min-w-0 flex-1 justify-center">
          {showMonthYearPickers ? (
            <div className="flex items-center gap-1 sm:gap-2 flex-wrap justify-center">
              <Select
                value={currentMonth.toString()}
                onValueChange={handleMonthChange}
              >
                <SelectTrigger
                  className="w-[100px] sm:w-[120px] h-7 sm:h-8 text-xs sm:text-sm"
                  size="sm"
                >
                  <SelectValue />
                </SelectTrigger>
                <SelectContent>
                  {MONTHS.map((month, index) => (
                    <SelectItem key={index} value={index.toString()}>
                      <span className="hidden sm:inline">{month}</span>
                      <span className="sm:hidden">{month.slice(0, 3)}</span>
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>

              <Select
                value={currentYear.toString()}
                onValueChange={handleYearChange}
              >
                <SelectTrigger
                  className="w-[70px] sm:w-[80px] h-7 sm:h-8 text-xs sm:text-sm"
                  size="sm"
                >
                  <SelectValue />
                </SelectTrigger>
                <SelectContent>
                  {yearRange.map((year) => (
                    <SelectItem key={year} value={year.toString()}>
                      {year}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
            </div>
          ) : (
            <motion.h2
              key={`${currentMonth}-${currentYear}`}
              initial={{ opacity: 0, y: -10 }}
              animate={{ opacity: 1, y: 0 }}
              className="text-base sm:text-lg font-semibold text-foreground text-center px-2"
            >
              <span className="hidden sm:inline">
                {MONTHS[currentMonth]} {currentYear}
              </span>
              <span className="sm:hidden">
                {MONTHS[currentMonth].slice(0, 3)} {currentYear}
              </span>
            </motion.h2>
          )}
        </div>

        <button
          onClick={() => navigateMonth("next")}
          className="inline-flex items-center justify-center rounded-ele p-1 sm:p-1.5 transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
          disabled={isAnimating}
        >
          <ChevronRight className="h-4 w-4" />
        </button>
      </div>
      {/* Days of week header */}
      <div className="grid grid-cols-7 gap-1">
        {DAYS_OF_WEEK.map((day) => (
          <div
            key={day}
            className="flex items-center justify-center h-7 sm:h-8 text-xs text-muted-foreground"
          >
            <span className="hidden sm:inline">{day}</span>
            <span className="sm:hidden">{day.slice(0, 1)}</span>
          </div>
        ))}
      </div>
      {/* Calendar grid */}
      <div className="relative overflow-hidden">
        <AnimatePresence mode="wait" custom={direction}>
          <motion.div
            key={`${currentMonth}-${currentYear}`}
            custom={direction}
            variants={slideVariants}
            initial="enter"
            animate="center"
            exit="exit"
            transition={{
              x: { type: "spring", stiffness: 500, damping: 30 },
              opacity: { duration: 0.2 },
            }}
            className="grid grid-cols-7 gap-0.5 sm:gap-1"
          >
            {/* Previous month days */}
            {showOutsideDays &&
              prevMonthDays.map((day) => (
                <button
                  key={`prev-${day}`}
                  onClick={() => handleDateClick(day, -1)}
                  className={cn(
                    dayVariants({ variant: getDayVariant(day, -1), size })
                  )}
                  disabled={isDateDisabled(
                    new Date(currentYear, currentMonth - 1, day)
                  )}
                >
                  {day}
                </button>
              ))}

            {/* Current month days */}
            {currentMonthDays.map((day) => (
              <button
                key={`current-${day}`}
                onClick={() => handleDateClick(day)}
                className={cn(
                  dayVariants({ variant: getDayVariant(day), size })
                )}
                disabled={isDateDisabled(
                  new Date(currentYear, currentMonth, day)
                )}
              >
                {day}
              </button>
            ))}

            {/* Next month days */}
            {showOutsideDays &&
              nextMonthDays.map((day) => (
                <button
                  key={`next-${day}`}
                  onClick={() => handleDateClick(day, 1)}
                  className={cn(
                    dayVariants({ variant: getDayVariant(day, 1), size })
                  )}
                  disabled={isDateDisabled(
                    new Date(currentYear, currentMonth + 1, day)
                  )}
                >
                  {day}
                </button>
              ))}
          </motion.div>
        </AnimatePresence>
      </div>
    </div>
  );
}

// Helper function to check if two dates are the same day
function isSameDay(date1: Date, date2: Date): boolean {
  return (
    date1.getDate() === date2.getDate() &&
    date1.getMonth() === date2.getMonth() &&
    date1.getFullYear() === date2.getFullYear()
  );
}

export { Calendar, calendarVariants, dayVariants, type CalendarProps };
npx hextaui@latest add calendar
pnpm dlx hextaui@latest add calendar
yarn dlx hextaui@latest add calendar
bun x hextaui@latest add calendar

Usage

import { Calendar } from "@/components/ui/Calendar";
<Calendar selected={selectedDate} onSelect={setSelectedDate} />

Examples

Disabled Dates

Jun 2025

S
M
T
W
T
F
S
export function CalendarDisabledDatesExample() {
    const [date, setDate] = React.useState<Date>();

    const disableWeekends = (date: Date) => {
        return date.getDay() === 0 || date.getDay() === 6; // Disable weekends
    };

    return (
        <Calendar selected={date} onSelect={setDate} disabled={disableWeekends} />
    );
}

With Month/Year Dropdowns

S
M
T
W
T
F
S
function CalendarWithSelectorsExample() {
  const [date, setDate] = React.useState<Date>();

  return (
    <Calendar
      selected={date}
      onSelect={setDate}
      showMonthYearPickers={true}
    />
  );
}

Date Range Selection

Jun 2025

S
M
T
W
T
F
S
  function CalendarRangeExample() {
    const [range, setRange] = React.useState<{ from: Date; to?: Date }>();

    return (
      <Calendar
        mode="range"
        selectedRange={range}
        onSelectRange={setRange}
      />
    );
  }

Multiple Date Selection

Jun 2025

S
M
T
W
T
F
S
function CalendarMultipleExample() {
  const [dates, setDates] = React.useState<Date[]>([]);

  return (
    <Calendar
      mode="multiple"
      selectedDates={dates}
      onSelectMultiple={setDates}
    />
  );
}

Calendar Sizes

Small Calendar

Jun 2025

S
M
T
W
T
F
S

Default Calendar

Jun 2025

S
M
T
W
T
F
S

Large Calendar

Jun 2025

S
M
T
W
T
F
S
function CalendarSizesExample() {
  const [date, setDate] = React.useState<Date>();

  return (
    <div className="space-y-6">
      <div>
        <h4 className="text-sm font-medium mb-2">Small Calendar</h4>
        <Calendar selected={date} onSelect={setDate} size="sm" />
      </div>
      <div>
        <h4 className="text-sm font-medium mb-2">Default Calendar</h4>
        <Calendar selected={date} onSelect={setDate} size="default" />
      </div>
      <div>
        <h4 className="text-sm font-medium mb-2">Large Calendar</h4>
        <Calendar selected={date} onSelect={setDate} size="lg" />
      </div>
    </div>
  );
}

Booking System Calendar

Perfect for reservation systems, hotels, or appointment booking.

Hotel Booking Calendar

Select an available date for your reservation. Grayed out dates are unavailable.

Jun 2025

S
M
T
W
T
F
S
function BookingCalendarExample() {
  const [selectedDate, setSelectedDate] = React.useState<Date>();

  // Mock booked dates (in a real app, this would come from your API)
  const bookedDates = [
    new Date(2025, 5, 15), // June 15, 2025
    new Date(2025, 5, 16), // June 16, 2025
    new Date(2025, 5, 20), // June 20, 2025
    new Date(2025, 5, 25), // June 25, 2025
  ];

  const isDateBooked = (date: Date) => {
    return bookedDates.some(bookedDate =>
      date.toDateString() === bookedDate.toDateString()
    );
  };

  const disableBookedDates = (date: Date) => {
    // Disable past dates and booked dates
    const today = new Date();
    today.setHours(0, 0, 0, 0);
    return date < today || isDateBooked(date);
  };

  return (
    <div className="space-y-4">
      <div>
        <h4 className="text-sm font-medium mb-2">Hotel Booking Calendar</h4>
        <p className="text-xs text-muted-foreground mb-4">
          Select an available date for your reservation. Grayed out dates are unavailable.
        </p>
      </div>
      <Calendar
        selected={selectedDate}
        onSelect={setSelectedDate}
        disabled={disableBookedDates}
        minDate={new Date()}
        maxDate={new Date(new Date().setMonth(new Date().getMonth() + 6))}
      />
      {selectedDate && (
        <p className="text-sm text-green-600">
          ✓ Selected: {selectedDate.toLocaleDateString()}
        </p>
      )}
    </div>
  );
}

Vacation Planner

Great for travel planning with range selection and day calculation.

Plan Your Vacation

Select your vacation start and end dates

S
M
T
W
T
F
S
function VacationPlannerExample() {
  const [vacationRange, setVacationRange] = React.useState<{ from: Date; to?: Date }>();

  const calculateDays = () => {
    if (vacationRange?.from && vacationRange?.to) {
      const diffTime = Math.abs(vacationRange.to.getTime() - vacationRange.from.getTime());
      const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1;
      return diffDays;
    }
    return 0;
  };

  return (
    <div className="space-y-4">
      <div>
        <h4 className="text-sm font-medium mb-2">Plan Your Vacation</h4>
        <p className="text-xs text-muted-foreground mb-4">
          Select your vacation start and end dates
        </p>
      </div>
      <Calendar
        mode="range"
        selectedRange={vacationRange}
        onSelectRange={setVacationRange}
        minDate={new Date()}
        showMonthYearPickers={true}
      />
      {vacationRange?.from && (
        <div className="p-3 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
          <p className="text-sm">
            <strong>Vacation Start:</strong> {vacationRange.from.toLocaleDateString()}
          </p>
          {vacationRange.to && (
            <>
              <p className="text-sm">
                <strong>Vacation End:</strong> {vacationRange.to.toLocaleDateString()}
              </p>
              <p className="text-sm font-medium text-blue-600 dark:text-blue-400">
                Total Days: {calculateDays()}
              </p>
            </>
          )}
        </div>
      )}
    </div>
  );
}

Task Scheduler

Perfect for project management and recurring task scheduling.

Schedule Tasks

Select multiple dates for recurring tasks

Jun 2025

S
M
T
W
T
F
S
function TaskSchedulerExample() {
  const [taskDates, setTaskDates] = React.useState<Date[]>([]);

  const handleDateToggle = (dates: Date[]) => {
    setTaskDates(dates);
  };

  return (
    <div className="space-y-4">
      <div>
        <h4 className="text-sm font-medium mb-2">Schedule Tasks</h4>
        <p className="text-xs text-muted-foreground mb-4">
          Select multiple dates for recurring tasks
        </p>
      </div>
      <Calendar
        mode="multiple"
        selectedDates={taskDates}
        onSelectMultiple={handleDateToggle}
        minDate={new Date()}
      />
      {taskDates.length > 0 && (
        <div className="p-3 bg-green-50 dark:bg-green-900/20 rounded-lg">
          <p className="text-sm font-medium mb-2">Scheduled Task Dates:</p>
          <div className="space-y-1">
            {taskDates.map((date, index) => (
              <p key={index} className="text-xs text-green-700 dark:text-green-300">
                • {date.toLocaleDateString('en-US', {
                  weekday: 'short',
                  month: 'short',
                  day: 'numeric'
                })}
              </p>
            ))}
          </div>
          <p className="text-xs text-green-600 dark:text-green-400 mt-2">
            Total: {taskDates.length} date{taskDates.length !== 1 ? 's' : ''} selected
          </p>
        </div>
      )}
    </div>
  );
}

Event Calendar

Show only dates with events and allow selection of event dates.

Event Calendar

Only dates with events are selectable

Jun 2025

S
M
T
W
T
F
S

Upcoming Events:

Team Meeting6/12/2025
Product Launch6/18/2025
Conference6/24/2025
function EventCalendarExample() {
  const [eventDate, setEventDate] = React.useState<Date>();

  // Mock events data
  const events = [
    { date: new Date(2025, 5, 12), title: "Team Meeting" },
    { date: new Date(2025, 5, 18), title: "Product Launch" },
    { date: new Date(2025, 5, 24), title: "Conference" },
  ];

  const hasEvent = (date: Date) => {
    return events.some(event =>
      date.toDateString() === event.date.toDateString()
    );
  };

  const disableEventDays = (date: Date) => {
    // Only allow selection of dates that have events
    return !hasEvent(date);
  };

  const getEventForDate = (date: Date) => {
    return events.find(event =>
      date.toDateString() === event.date.toDateString()
    );
  };

  return (
    <div className="space-y-4">
      <div>
        <h4 className="text-sm font-medium mb-2">Event Calendar</h4>
        <p className="text-xs text-muted-foreground mb-4">
          Only dates with events are selectable
        </p>
      </div>
      <Calendar
        selected={eventDate}
        onSelect={setEventDate}
        disabled={disableEventDays}
      />
      <div className="space-y-2">
        <p className="text-sm font-medium">Upcoming Events:</p>
        {events.map((event, index) => (
          <div key={index} className="flex justify-between items-center p-2 bg-gray-50 dark:bg-gray-800 rounded">
            <span className="text-sm">{event.title}</span>
            <span className="text-xs text-muted-foreground">
              {event.date.toLocaleDateString()}
            </span>
          </div>
        ))}
      </div>
      {eventDate && (
        <div className="p-3 bg-purple-50 dark:bg-purple-900/20 rounded-lg">
          <p className="text-sm">
            <strong>Selected Event:</strong> {getEventForDate(eventDate)?.title}
          </p>
          <p className="text-xs text-muted-foreground">
            {eventDate.toLocaleDateString()}
          </p>
        </div>
      )}
    </div>
  );
}

Age Verification Calendar

Useful for forms requiring age verification with date restrictions.

Age Verification (18+)

Select your birth date. Must be 18 or older.

S
M
T
W
T
F
S
function AgeRestrictedCalendarExample() {
  const [birthDate, setBirthDate] = React.useState<Date>();

  const isUnder18 = (date: Date) => {
    const today = new Date();
    const eighteenYearsAgo = new Date(today.getFullYear() - 18, today.getMonth(), today.getDate());
    return date > eighteenYearsAgo;
  };

  const calculateAge = (birthDate: Date) => {
    const today = new Date();
    let age = today.getFullYear() - birthDate.getFullYear();
    const monthDiff = today.getMonth() - birthDate.getMonth();

    if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
      age--;
    }

    return age;
  };

  return (
    <div className="space-y-4">
      <div>
        <h4 className="text-sm font-medium mb-2">Age Verification (18+)</h4>
        <p className="text-xs text-muted-foreground mb-4">
          Select your birth date. Must be 18 or older.
        </p>
      </div>
      <Calendar
        selected={birthDate}
        onSelect={setBirthDate}
        disabled={isUnder18}
        maxDate={new Date()}
        showMonthYearPickers={true}
      />
      {birthDate && (
        <div className="p-3 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
          <p className="text-sm">
            <strong>Birth Date:</strong> {birthDate.toLocaleDateString()}
          </p>
          <p className="text-sm">
            <strong>Age:</strong> {calculateAge(birthDate)} years old
          </p>
          <p className={`text-sm font-medium ${calculateAge(birthDate) >= 18 ? 'text-green-600' : 'text-red-600'}`}>
            {calculateAge(birthDate) >= 18 ? '✓ Eligible' : '✗ Must be 18 or older'}
          </p>
        </div>
      )}
    </div>
  );
}

Responsive Design

Perfect for adapting to different screen sizes automatically.

Responsive Calendar

Calendar adapts to different screen sizes with optimized dropdowns and layout

S
M
T
W
T
F
S
function ResponsiveCalendarExample() {
  const [date, setDate] = React.useState<Date>();

  return (
    <div className="w-full max-w-xs mx-auto">
      <div className="mb-4">
        <h4 className="text-sm font-medium mb-2">Responsive Calendar</h4>
        <p className="text-xs text-muted-foreground mb-4">
          Calendar adapts to different screen sizes with optimized dropdowns and layout
        </p>
      </div>
      <Calendar
        selected={date}
        onSelect={setDate}
        showMonthYearPickers={true}
        className="w-full"
      />
      {date && (
        <div className="mt-4 p-3 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
          <p className="text-sm">
            <strong>Selected:</strong> {date.toLocaleDateString()}
          </p>
        </div>
      )}
    </div>
  );
}

Mobile-Optimized

Specifically designed for mobile devices with larger touch targets.

Mobile-Optimized Range Selection

Optimized for touch interactions with larger touch targets

S
M
T
W
T
F
S
function MobileCalendarExample() {
  const [range, setRange] = React.useState<{ from: Date; to?: Date }>();

  return (
    <div className="w-full max-w-sm mx-auto">
      <div className="mb-4">
        <h4 className="text-sm font-medium mb-2">Mobile-Optimized Range Selection</h4>
        <p className="text-xs text-muted-foreground mb-4">
          Optimized for touch interactions with larger touch targets
        </p>
      </div>
      <Calendar
        mode="range"
        selectedRange={range}
        onSelectRange={setRange}
        showMonthYearPickers={true}
        size="default"
        className="w-full"
      />
      {range?.from && (
        <div className="mt-4 p-3 bg-green-50 dark:bg-green-900/20 rounded-lg">
          <p className="text-sm">
            <strong>Start:</strong> {range.from.toLocaleDateString()}
          </p>
          {range.to && (
            <p className="text-sm">
              <strong>End:</strong> {range.to.toLocaleDateString()}
            </p>
          )}
        </div>
      )}
    </div>
  );
}

Props

PropTypeDefault
className?
string
undefined
locale?
string
"en-US"
alwaysOnTop?
boolean
true
showMonthYearPickers?
boolean
false
showOutsideDays?
boolean
true
maxDate?
Date
undefined
minDate?
Date
undefined
size?
"sm" | "default" | "lg"
"default"
disabled?
(date: Date) => boolean
undefined
onSelectRange?
(range: { from: Date; to: Date }) => void
undefined
onSelectMultiple?
(dates: Date[]) => void
undefined
selectedRange?
{ from: Date; to: Date }
undefined
selectedDates?
Date[]
[]
mode?
"single" | "multiple" | "range"
"single"
onSelect?
(date: Date) => void
undefined
selected?
Date
undefined
Edit on GitHub

Last updated on