Beta

Inputs

Calendar

A component for entering and editing a single date or a range of dates.

MoTuWeThFrSaSu

Selected Date: None

Installation

  1. 1

    Install dependencies:

    npm install react-day-picker date-fns @remixicon/react
  2. 2

    Add component:

    Copy and paste the code into your project’s component directory. Do not forget to update the import paths.
    // Tremor Raw Calendar [v0.0.1]
    "use client"
    import * as React from "react"import {  RiArrowLeftDoubleLine,  RiArrowLeftSLine,  RiArrowRightDoubleLine,  RiArrowRightSLine,} from "@remixicon/react"import { addYears, format, isSameMonth } from "date-fns"import {  DayPicker,  useDayPicker,  useDayRender,  useNavigation,  type DayPickerRangeProps,  type DayPickerSingleProps,  type DayProps,  type Matcher,} from "react-day-picker"
    import { cx, focusRing } from "@/lib/utils"
    interface NavigationButtonProps  extends React.HTMLAttributes<HTMLButtonElement> {  onClick: () => void  icon: React.ElementType  disabled?: boolean}
    const NavigationButton = React.forwardRef<  HTMLButtonElement,  NavigationButtonProps>(  (    { onClick, icon, disabled, ...props }: NavigationButtonProps,    forwardedRef,  ) => {    const Icon = icon    return (      <button        ref={forwardedRef}        type="button"        disabled={disabled}        className={cx(          "flex size-8 shrink-0 select-none items-center justify-center rounded border p-1 outline-none transition sm:size-[30px]",          // text color          "text-gray-600 hover:text-gray-800",          "dark:text-gray-400 hover:dark:text-gray-200",          // border color          "border-gray-300 dark:border-gray-700",          // background color          "hover:bg-gray-50 active:bg-gray-100",          "hover:dark:bg-gray-900 active:dark:bg-gray-800",          // disabled          "disabled:pointer-events-none",          "disabled:border-gray-200 disabled:dark:border-gray-800",          "disabled:text-gray-400 disabled:dark:text-gray-600",          focusRing,        )}        onClick={onClick}        {...props}      >        <Icon className="size-full shrink-0" />      </button>    )  },)
    NavigationButton.displayName = "NavigationButton"
    type OmitKeys<T, K extends keyof T> = {  [P in keyof T as P extends K ? never : P]: T[P]}
    type KeysToOmit = "showWeekNumber" | "captionLayout" | "mode"
    type SingleProps = OmitKeys<DayPickerSingleProps, KeysToOmit>type RangeProps = OmitKeys<DayPickerRangeProps, KeysToOmit>
    type CalendarProps =  | ({      mode: "single"    } & SingleProps)  | ({      mode?: undefined    } & SingleProps)  | ({      mode: "range"    } & RangeProps)
    const Calendar = ({  mode = "single",  weekStartsOn = 1,  numberOfMonths = 1,  enableYearNavigation = false,  disableNavigation,  locale,  className,  classNames,  ...props}: CalendarProps & { enableYearNavigation?: boolean }) => {  return (    <DayPicker      mode={mode}      weekStartsOn={weekStartsOn}      numberOfMonths={numberOfMonths}      locale={locale}      showOutsideDays={numberOfMonths === 1 ? true : false}      className={cx(className)}      classNames={{        months: "flex space-y-0",        month: "space-y-4 p-3",        nav: "gap-1 flex items-center rounded-full size-full justify-between p-4",        table: "w-full border-collapse space-y-1",        head_cell:          "w-9 font-medium text-sm sm:text-xs text-center text-gray-400 dark:text-gray-600 pb-2",        row: "w-full mt-0.5",        cell: cx(          "relative p-0 text-center focus-within:relative",          "text-gray-900 dark:text-gray-50",        ),        day: cx(          "size-9 rounded text-sm text-gray-900 dark:text-gray-50",          "hover:bg-gray-200 hover:dark:bg-gray-700",          focusRing,        ),        day_today: "font-semibold",        day_selected: cx(          "rounded",          "aria-selected:bg-gray-900 aria-selected:text-gray-50",          "dark:aria-selected:bg-gray-50 dark:aria-selected:text-gray-900",        ),        day_disabled:          "!text-gray-300 dark:!text-gray-700 line-through disabled:hover:bg-transparent",        day_outside: "text-gray-400 dark:text-gray-600",        day_range_middle: cx(          "!rounded-none",          "aria-selected:!bg-gray-100 aria-selected:!text-gray-900",          "dark:aria-selected:!bg-gray-900 dark:aria-selected:!text-gray-50",        ),        day_range_start: "rounded-r-none !rounded-l",        day_range_end: "rounded-l-none !rounded-r",        day_hidden: "invisible",        ...classNames,      }}      components={{        IconLeft: () => (          <RiArrowLeftSLine aria-hidden="true" className="size-4" />        ),        IconRight: () => (          <RiArrowRightSLine aria-hidden="true" className="size-4" />        ),        Caption: ({ ...props }) => {          const {            goToMonth,            nextMonth,            previousMonth,            currentMonth,            displayMonths,          } = useNavigation()          const { numberOfMonths, fromDate, toDate } = useDayPicker()
              const displayIndex = displayMonths.findIndex((month) =>            isSameMonth(props.displayMonth, month),          )          const isFirst = displayIndex === 0          const isLast = displayIndex === displayMonths.length - 1
              const hideNextButton = numberOfMonths > 1 && (isFirst || !isLast)          const hidePreviousButton = numberOfMonths > 1 && (isLast || !isFirst)
              const goToPreviousYear = () => {            const targetMonth = addYears(currentMonth, -1)            if (              previousMonth &&              (!fromDate || targetMonth.getTime() >= fromDate.getTime())            ) {              goToMonth(targetMonth)            }          }
              const goToNextYear = () => {            const targetMonth = addYears(currentMonth, 1)            if (              nextMonth &&              (!toDate || targetMonth.getTime() <= toDate.getTime())            ) {              goToMonth(targetMonth)            }          }
              return (            <div className="flex items-center justify-between">              <div className="flex items-center gap-1">                {enableYearNavigation && !hidePreviousButton && (                  <NavigationButton                    disabled={                      disableNavigation ||                      !previousMonth ||                      (fromDate &&                        addYears(currentMonth, -1).getTime() <                          fromDate.getTime())                    }                    aria-label="Go to previous year"                    onClick={goToPreviousYear}                    icon={RiArrowLeftDoubleLine}                  />                )}                {!hidePreviousButton && (                  <NavigationButton                    disabled={disableNavigation || !previousMonth}                    aria-label="Go to previous month"                    onClick={() => previousMonth && goToMonth(previousMonth)}                    icon={RiArrowLeftSLine}                  />                )}              </div>
                  <div                role="presentation"                aria-live="polite"                className="text-sm font-medium capitalize tabular-nums text-gray-900 dark:text-gray-50"              >                {format(props.displayMonth, "LLLL yyy", { locale })}              </div>
                  <div className="flex items-center gap-1">                {!hideNextButton && (                  <NavigationButton                    disabled={disableNavigation || !nextMonth}                    aria-label="Go to next month"                    onClick={() => nextMonth && goToMonth(nextMonth)}                    icon={RiArrowRightSLine}                  />                )}                {enableYearNavigation && !hideNextButton && (                  <NavigationButton                    disabled={                      disableNavigation ||                      !nextMonth ||                      (toDate &&                        addYears(currentMonth, 1).getTime() > toDate.getTime())                    }                    aria-label="Go to next year"                    onClick={goToNextYear}                    icon={RiArrowRightDoubleLine}                  />                )}              </div>            </div>          )        },        Day: ({ date, displayMonth }: DayProps) => {          const buttonRef = React.useRef<HTMLButtonElement>(null)          const { activeModifiers, buttonProps, divProps, isButton, isHidden } =            useDayRender(date, displayMonth, buttonRef)
              const { selected, today, disabled, range_middle } = activeModifiers
              if (isHidden) {            return <></>          }
              if (!isButton) {            return (              <div                {...divProps}                className={cx(                  "flex items-center justify-center",                  divProps.className,                )}              />            )          }
              const {            children: buttonChildren,            className: buttonClassName,            ...buttonPropsRest          } = buttonProps
              return (            <button              ref={buttonRef}              {...buttonPropsRest}              type="button"              className={cx("relative", buttonClassName)}            >              {buttonChildren}              {today && (                <span                  className={cx(                    "absolute inset-x-1/2 bottom-1.5 h-0.5 w-4 -translate-x-1/2 rounded-[2px]",                    {                      "bg-blue-500 dark:bg-blue-500": !selected,                      "!bg-white dark:!bg-gray-950": selected,                      "!bg-gray-400 dark:!bg-gray-600":                        selected && range_middle,                      "text-gray-400 dark:text-gray-600": disabled,                    },                  )}                />              )}            </button>          )        },      }}      {...(props as SingleProps & RangeProps)}    />  )}
    Calendar.displayName = "Calendar"
    export { Calendar, type Matcher }

Example: With year navigation

MoTuWeThFrSaSu

Selected Date: None

Example: With disabled navigation

MoTuWeThFrSaSu

Selected Date: None

Example: With range and two months

MoTuWeThFrSaSu
MoTuWeThFrSaSu

Selected Range: None

Example: With French locale

lumamejevesadi

Date sélectionnée: None

Example: With no dates after today

MoTuWeThFrSaSu

Selected date: None

API Reference: Calendar

This component uses the React Day Picker API. Note that showWeekNumber and captionLayout are omitted.

mode
"single" | "range"
Selection mode of the calendar.

Default: "single"

locale
Locale
The locale used for date formatting. Accepts a date-fns locale object.
weekStartsOn
0 | 2 | 1 | 3 | 4 | 5 | 6
Index of the first day of the week (Sunday = 0)

Default: 1

numberOfMonths
number
The number of months to be displayed.

Default: 1

enableYearNavigation
boolean
Add additional buttons to switch years.

Default: false

disableNavigation
boolean
Disable the year and month navigation.

Default: false