Inputs
Calendar
A component for entering and editing a single date or a range of dates.
Selected Date: None
"use client"
import React from "react"import { DateRange } from "react-day-picker"
import { Calendar } from "@/components/Calendar"import { Label } from "@/components/Label"import { Switch } from "@/components/Switch"
export const CalendarHero = () => { const [date, setDate] = React.useState<Date | undefined>(undefined) const [dateRange, setDateRange] = React.useState<DateRange | undefined>( undefined, ) const [mode, setMode] = React.useState<"single" | "range">("single") return ( <div className="flex flex-col items-center gap-6"> <div className="flex items-center gap-2"> <Switch size="small" id="range-switch" checked={mode === "single" ? false : true} onCheckedChange={() => { setMode(mode === "single" ? "range" : "single") }} /> <Label htmlFor="range-switch">Range selection & multiple months</Label> </div> <> {mode === "single" && ( <div className="flex flex-col items-center gap-y-4"> <Calendar selected={date} onSelect={setDate} /> <p className="rounded bg-gray-100 p-2 text-sm text-gray-500 dark:bg-gray-800 dark:text-gray-300"> Selected Date: {date ? date.toLocaleDateString() : "None"} </p> </div> )} {mode === "range" && ( <div className="flex flex-col items-center gap-y-4"> <Calendar mode="range" numberOfMonths={2} selected={dateRange} onSelect={setDateRange} /> <p className="rounded bg-gray-100 p-2 text-sm text-gray-500 dark:bg-gray-800 dark:text-gray-300"> Selected Range:{" "} {dateRange ? `${dateRange.from?.toLocaleDateString()} – ${dateRange.to?.toLocaleDateString() ?? ""}` : "None"} </p> </div> )} </> </div> )}
Installation
npm install react-day-picker@8.10.1 date-fns @remixicon/react
- Copy and paste the code into your project’s component directory. Do not forget to update the import paths.
// Tremor Calendar [v0.1.0] "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-800", // 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} 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 focus:z-10", "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-blue-500 aria-selected:text-white", "dark:aria-selected:bg-blue-500 dark:aria-selected:text-white", ), 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, "bg-gray-400 text-gray-400 dark:bg-gray-400 dark:text-gray-600": disabled, }, )} /> )} </button> ) }, }} tremor-id="tremor-raw" {...(props as SingleProps & RangeProps)} /> )} Calendar.displayName = "Calendar" export { Calendar, type Matcher }
Example: With year navigation
Selected Date: None
"use client"
import React from "react"import { Calendar } from "@/components/Calendar"
export const CalendarYearNavigationExample = () => { const [date, setDate] = React.useState<Date | undefined>(undefined) return ( <div className="flex flex-col items-center gap-y-4"> <Calendar enableYearNavigation selected={date} onSelect={setDate} /> <p className="rounded bg-gray-100 p-2 text-sm text-gray-500 dark:bg-gray-800 dark:text-gray-300"> Selected Date: {date ? date.toLocaleDateString() : "None"} </p> </div> )}
Example: With disabled navigation
Selected Date: None
"use client"
import React from "react"import { Calendar } from "@/components/Calendar"
export const CalendarDisableNavigationExample = () => { const [date, setDate] = React.useState<Date | undefined>(undefined) return ( <div className="flex flex-col items-center gap-y-4"> <Calendar enableYearNavigation disableNavigation={true} selected={date} onSelect={setDate} /> <p className="rounded bg-gray-100 p-2 text-sm text-gray-500 dark:bg-gray-800 dark:text-gray-300"> Selected Date: {date ? date.toLocaleDateString() : "None"} </p> </div> )}
Example: With range and two months
Selected Range: None
"use client"
import React from "react"import { DateRange } from "react-day-picker"import { Calendar } from "@/components/Calendar"
export const CalendarRangeExample = () => { const [dateRange, setDateRange] = React.useState<DateRange | undefined>( undefined, ) return ( <div className="flex flex-col items-center gap-y-4"> <Calendar mode="range" numberOfMonths={2} selected={dateRange} onSelect={setDateRange} /> <p className="rounded bg-gray-100 p-2 text-sm text-gray-500 dark:bg-gray-800 dark:text-gray-300"> Selected Range:{" "} {dateRange ? `${dateRange.from?.toLocaleDateString()} – ${dateRange.to?.toLocaleDateString() ?? ""}` : "None"} </p> </div> )}
Example: With French locale
Date sélectionnée: None
"use client"
import React from "react"import { fr } from "date-fns/locale"import { Calendar } from "@/components/Calendar"
export const CalendarLocaleExample = () => { const [date, setDate] = React.useState<Date | undefined>(undefined) return ( <div className="flex flex-col items-center gap-y-4"> <Calendar locale={fr} selected={date} onSelect={setDate} /> <p className="rounded bg-gray-100 p-2 text-sm text-gray-500 dark:bg-gray-800 dark:text-gray-300"> Date sélectionnée: {date ? date.toLocaleDateString() : "None"} </p> </div> )}
Example: With no dates after today
Selected date: None
"use client"
import React from "react"import { Calendar } from "@/components/Calendar"
export const CalendarToTodayExample = () => { const [date, setDate] = React.useState<Date | undefined>(undefined) return ( <div className="flex flex-col items-center gap-y-4"> <Calendar enableYearNavigation toDate={new Date()} selected={date} onSelect={setDate} /> <p className="rounded bg-gray-100 p-2 text-sm text-gray-500 dark:bg-gray-800 dark:text-gray-300"> Selected date: {date ? date.toLocaleDateString() : "None"} </p> </div> )}
API Reference: Calendar
This component uses the React Day Picker API. Note that showWeekNumber and captionLayout are omitted.
- mode
- Selection mode of the calendar.
"single" | "range"
Default: "single"
- locale
- The locale used for date formatting. Accepts a date-fns locale object.
Locale
- weekStartsOn
- Index of the first day of the week (Sunday = 0)
0 | 2 | 1 | 3 | 4 | 5 | 6
Default: 1
- numberOfMonths
- The number of months to be displayed.
number
Default: 1
- enableYearNavigation
- Add additional buttons to switch years.
boolean
Default: false
- disableNavigation
- Disable the year and month navigation.
boolean
Default: false