Components

Base Components

Calendar

A calendar component for selecting a date or a range of dates.

Example

April 2026

Installation

Install the following dependencies:

npm install react-day-pickernpm install date-fns

Create a calendar.tsx file and paste the following code into it.

"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-surface 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"            ? "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,        ),        table: "w-full border-collapse",        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      ref={ref}      variant="ghost"      size="sm"      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 };

Check the import paths to ensure they match your project setup.

Usage

import { Calendar } from "@/components/ui/calendar";
const [date, setDate] = React.useState<Date | undefined>(new Date());

return (
  <Calendar
    mode="single"
    selected={date}
    onSelect={setDate}
    className="rounded-lg border"
  />
);
See the React DayPicker documentation for more information.

About

The Calendar component is built on top of React DayPicker.

Date Picker

You can use the Calendar component to build a date picker by combining it with a Popover and an Input.

Persian / Hijri / Jalali Calendar

To use the Persian calendar, edit components/ui/calendar.tsx and replace the import:
- import { DayPicker } from "react-day-picker"
+ import { DayPicker } from "react-day-picker/persian"

Selected Date (With TimeZone)

The Calendar component accepts a timeZone prop so dates are displayed and selected in the user's local timezone.
April 2026
export function CalendarWithTimezone() {
  const [date, setDate] = React.useState<Date | undefined>(undefined);
  const [timeZone, setTimeZone] = React.useState<string | undefined>(undefined);

  React.useEffect(() => {
    setTimeZone(Intl.DateTimeFormat().resolvedOptions().timeZone);
  }, []);

  return (
    <Calendar
      mode="single"
      selected={date}
      onSelect={setDate}
      timeZone={timeZone}
      className="rounded-lg border"
    />
  );
}
Note: If the selected date appears offset (e.g. selecting the 20th highlights the 19th), set the timeZone prop to the user's local timezone. Why client-side? Timezone is detected with Intl.DateTimeFormat().resolvedOptions().timeZone inside useEffect to avoid hydration mismatches when server and client timezones differ.

Examples

Basic

A basic calendar with single-date selection. Use className="rounded-lg border" to style the calendar.
April 2026
"use client";

import { Calendar } from "@/components/ui/calendar";

export function CalendarBasic() {
  const [date, setDate] = React.useState<Date | undefined>(new Date());
  return (
    <Calendar
      mode="single"
      selected={date}
      onSelect={setDate}
      className="rounded-lg border"
    />
  );
}

Range Calendar

Use mode="range" to enable range selection.
April 2026
"use client";

import { Calendar } from "@/components/ui/calendar";

export function CalendarRange() {
  const [range, setRange] = React.useState<
    { from?: Date; to?: Date } | undefined
  >(undefined);
  return (
    <Calendar
      mode="range"
      selected={range}
      onSelect={setRange}
      className="rounded-lg border"
    />
  );
}

Month and Year Selector

Use captionLayout="dropdown" to show month and year dropdowns.
April 2026
<Calendar
  mode="single"
  selected={date}
  onSelect={setDate}
  captionLayout="dropdown"
  fromYear={1926}
  toYear={2026}
  className="rounded-lg border"
/>

Custom Cell Size

Customize the size of calendar cells with the --cell-size CSS variable. You can use breakpoint-specific values:
<Calendar
  mode="single"
  selected={date}
  onSelect={setDate}
  className="rounded-lg border [--cell-size:--spacing(11)] md:[--cell-size:--spacing(12)]"
/>
Or use fixed values:
<Calendar
  mode="single"
  selected={date}
  onSelect={setDate}
  className="rounded-lg border [--cell-size:2.75rem] md:[--cell-size:3rem]"
/>

Week Numbers

Use showWeekNumber to show week numbers.
April 2026
14
15
16
17
18
<Calendar
  mode="single"
  selected={date}
  onSelect={setDate}
  showWeekNumber
  className="rounded-lg border"
/>

RTL

When using RTL, import the locale from react-day-picker/locale and pass both the locale and dir props to the Calendar:
import { arSA } from "react-day-picker/locale";

<Calendar
  mode="single"
  selected={date}
  onSelect={setDate}
  locale={arSA}
  dir="rtl"
  className="rounded-lg border"
/>;

API Reference

See the React DayPicker documentation for more information on the Calendar component props and behavior.

On this page