Shadcn Date Picker
componentdate-pickersource: ok · hit
Date Picker
Overwrite the default calendar component with fixed styles for `react-day-picker` v9 and above.
npx shadcn add "https://date-picker.luca-felix.com/r/date-picker.json"free
Where it goes
calendar
Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.
Dependencies
Bundle weight · lightRegistry dependencies
buttonpopover
Files5 · source cached 10/14/2026, 8:12:50 AM
src/registry/new-york/components/ui/calendar.tsx
"use client"
import { Button, buttonVariants } from "@/components/ui/button"
import { cn } from "@/lib/utils"
import { differenceInCalendarDays } from "date-fns"
import { ChevronLeft, ChevronRight } from "lucide-react"
import * as React from "react"
import {
DayPicker,
labelNext,
labelPrevious,
useDayPicker,
type DayPickerProps,
} from "react-day-picker"
export type CalendarProps = DayPickerProps & {
/**
* In the year view, the number of years to display at once.
* @default 12
*/
yearRange?: number
/**
* Wether to show the year switcher in the caption.
* @default true
*/
showYearSwitcher?: boolean
monthsClassName?: string
monthCaptionClassName?: string
weekdaysClassName?: string
weekdayClassName?: string
monthClassName?: string
captionClassName?: string
captionLabelClassName?: string
buttonNextClassName?: string
buttonPreviousClassName?: string
navClassName?: string
monthGridClassName?: string
weekClassName?: string
dayClassName?: string
dayButtonClassName?: string
rangeStartClassName?: string
rangeEndClassName?: string
selectedClassName?: string
todayClassName?: string
outsideClassName?: string
disabledClassName?: string
rangeMiddleClassName?: string
hiddenClassName?: string
}
type NavView = "days" | "years"
/**
* A custom calendar component built on top of react-day-picker.
* @param props The props for the calendar.
* @default yearRange 12
* @returns
*/
function Calendar({
className,
showOutsideDays = true,
showYearSwitcher = true,
yearRange = 12,
numberOfMonths,
components,
...props
}: CalendarProps) {
const [navView, setNavView] = React.useState<NavView>("days")
const [displayYears, setDisplayYears] = React.useState<{
from: number
to: number
}>(
React.useMemo(() => {
const currentYear = new Date().getFullYear()
return {
from: currentYear - Math.floor(yearRange / 2 - 1),
to: currentYear + Math.ceil(yearRange / 2),
}
}, [yearRange])
)
const { onNextClick, onPrevClick, startMonth, endMonth } = props
const columnsDisplayed = navView === "years" ? 1 : numberOfMonths
const _monthsClassName = cn("relative flex", props.monthsClassName)
const _monthCaptionClassName = cn(
"relative mx-10 flex h-7 items-center justify-center",
props.monthCaptionClassName
)
const _weekdaysClassName = cn("flex flex-row", props.weekdaysClassName)
const _weekdayClassName = cn(
"w-8 text-sm font-normal text-muted-foreground",
props.weekdayClassName
)
const _monthClassName = cn("w-full", props.monthClassName)
const _captionClassName = cn(
"relative flex items-center justify-center pt-1",
props.captionClassName
)
const _captionLabelClassName = cn(
"truncate text-sm font-medium",
props.captionLabelClassName
)
const buttonNavClassName = buttonVariants({
variant: "outline",
className:
"absolute h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100",
})
const _buttonNextClassName = cn(
buttonNavClassName,
"right-0",
props.buttonNextClassName
)
const _buttonPreviousClassName = cn(
buttonNavClassName,
"left-0",
props.buttonPreviousClassName
)
const _navClassName = cn("flex items-start", props.navClassName)
const _monthGridClassName = cn("mx-auto mt-4", props.monthGridClassName)
const _weekClassName = cn("mt-2 flex w-max items-start", props.weekClassName)
const _dayClassName = cn(
"flex size-8 flex-1 items-center justify-center p-0 text-sm",
props.dayClassName
)
const _dayButtonClassName = cn(
buttonVariants({ variant: "ghost" }),
"size-8 rounded-md p-0 font-normal transition-none aria-selected:opacity-100",
props.dayButtonClassName
)
const buttonRangeClassName =
"bg-accent [&>button]:bg-primary [&>button]:text-primary-foreground [&>button]:hover:bg-primary [&>button]:hover:text-primary-foreground"
const _rangeStartClassName = cn(
buttonRangeClassName,
"day-range-start rounded-s-md",
props.rangeStartClassName
)
const _rangeEndClassName = cn(
buttonRangeClassName,
"day-range-end rounded-e-md",
props.rangeEndClassName
)
const _rangeMiddleClassName = cn(
"bg-accent !text-foreground [&>button]:bg-transparent [&>button]:!text-foreground [&>button]:hover:bg-transparent [&>button]:hover:!text-foreground",
props.rangeMiddleClassName
)
const _selectedClassName = cn(
"[&>button]:bg-primary [&>button]:text-primary-foreground [&>button]:hover:bg-primary [&>button]:hover:text-primary-foreground",
props.selectedClassName
)
const _todayClassName = cn(
"[&>button]:bg-accent [&>button]:text-accent-foreground",
props.todayClassName
)
const _outsideClassName = cn(
"day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30",
props.outsideClassName
)
const _disabledClassName = cn(
"text-muted-foreground opacity-50",
props.disabledClassName
)
const _hiddenClassName = cn("invisible flex-1", props.hiddenClassName)
return (
<DayPicker
showOutsideDays={showOutsideDays}
className={cn("p-3", className)}
style={{
width: 248.8 * (columnsDisplayed ?? 1) + "px",
}}
classNames={{
months: _monthsClassName,
month_caption: _monthCaptionClassName,
weekdays: _weekdaysClassName,
weekday: _weekdayClassName,
month: _monthClassName,
caption: _captionClassName,
caption_label: _captionLabelClassName,
button_next: _buttonNextClassName,
button_previous: _buttonPreviousClassName,
nav: _navClassName,
month_grid: _monthGridClassName,
week: _weekClassName,
day: _dayClassName,
day_button: _dayButtonClassName,
range_start: _rangeStartClassName,
range_middle: _rangeMiddleClassName,
range_end: _rangeEndClassName,
selected: _selectedClassName,
today: _todayClassName,
outside: _outsideClassName,
disabled: _disabledClassName,
hidden: _hiddenClassName,
}}
components={{
Chevron: ({ orientation }) => {
const Icon = orientation === "left" ? ChevronLeft : ChevronRight
return <Icon className="h-4 w-4" />
},
Nav: ({ className }) => (
<Nav
className={className}
displayYears={displayYears}
navView={navView}
setDisplayYears={setDisplayYears}
startMonth={startMonth}
endMonth={endMonth}
onPrevClick={onPrevClick}
onNextClick={onNextClick}
/>
),
CaptionLabel: (props) => (
<CaptionLabel
showYearSwitcher={showYearSwitcher}
navView={navView}
setNavView={setNavView}
displayYears={displayYears}
{...props}
/>
),
MonthGrid: ({ className, children, ...props }) => (
<MonthGrid
children={children}
className={className}
displayYears={displayYears}
startMonth={startMonth}
endMonth={endMonth}
navView={navView}
setNavView={setNavView}
{...props}
/>
),
...components,
}}
numberOfMonths={columnsDisplayed}
{...props}
/>
)
}
Calendar.displayName = "Calendar"
function Nav({
className,
navView,
startMonth,
endMonth,
displayYears,
setDisplayYears,
onPrevClick,
onNextClick,
}: {
className?: string
navView: NavView
startMonth?: Date
endMonth?: Date
displayYears: { from: number; to: number }
setDisplayYears: React.Dispatch<
React.SetStateAction<{ from: number; to: number }>
>
onPrevClick?: (date: Date) => void
onNextClick?: (date: Date) => void
}) {
const { nextMonth, previousMonth, goToMonth } = useDayPicker()
const isPreviousDisabled = (() => {
if (navView === "years") {
return (
(startMonth &&
differenceInCalendarDays(
new Date(displayYears.from - 1, 0, 1),
startMonth
) < 0) ||
(endMonth &&
differenceInCalendarDays(
new Date(displayYears.from - 1, 0, 1),
endMonth
) > 0)
)
}
return !previousMonth
})()
const isNextDisabled = (() => {
if (navView === "years") {
return (
(startMonth &&
differenceInCalendarDays(
new Date(displayYears.to + 1, 0, 1),
startMonth
) < 0) ||
(endMonth &&
differenceInCalendarDays(
new Date(displayYears.to + 1, 0, 1),
endMonth
) > 0)
)
}
return !nextMonth
})()
const handlePreviousClick = React.useCallback(() => {
if (!previousMonth) return
if (navView === "years") {
setDisplayYears((prev) => ({
from: prev.from - (prev.to - prev.from + 1),
to: prev.to - (prev.to - prev.from + 1),
}))
onPrevClick?.(
new Date(
displayYears.from - (displayYears.to - displayYears.from),
0,
1
)
)
return
}
goToMonth(previousMonth)
onPrevClick?.(previousMonth)
}, [previousMonth, goToMonth])
const handleNextClick = React.useCallback(() => {
if (!nextMonth) return
if (navView === "years") {
setDisplayYears((prev) => ({
from: prev.from + (prev.to - prev.from + 1),
to: prev.to + (prev.to - prev.from + 1),
}))
onNextClick?.(
new Date(
displayYears.from + (displayYears.to - displayYears.from),
0,
1
)
)
return
}
goToMonth(nextMonth)
onNextClick?.(nextMonth)
}, [goToMonth, nextMonth])
return (
<nav className={cn("flex items-center", className)}>
<Button
variant="outline"
className="absolute left-0 h-7 w-7 bg-transparent p-0 opacity-80 hover:opacity-100"
type="button"
tabIndex={isPreviousDisabled ? undefined : -1}
disabled={isPreviousDisabled}
aria-label={
navView === "years"
? `Go to the previous ${
displayYears.to - displayYears.from + 1
} years`
: labelPrevious(previousMonth)
}
onClick={handlePreviousClick}
>
<ChevronLeft className="h-4 w-4" />
</Button>
<Button
variant="outline"
className="absolute right-0 h-7 w-7 bg-transparent p-0 opacity-80 hover:opacity-100"
type="button"
tabIndex={isNextDisabled ? undefined : -1}
disabled={isNextDisabled}
aria-label={
navView === "years"
? `Go to the next ${displayYears.to - displayYears.from + 1} years`
: labelNext(nextMonth)
}
onClick={handleNextClick}
>
<ChevronRight className="h-4 w-4" />
</Button>
</nav>
)
}
function CaptionLabel({
children,
showYearSwitcher,
navView,
setNavView,
displayYears,
...props
}: {
showYearSwitcher?: boolean
navView: NavView
setNavView: React.Dispatch<React.SetStateAction<NavView>>
displayYears: { from: number; to: number }
} & React.HTMLAttributes<HTMLSpanElement>) {
if (!showYearSwitcher) return <span {...props}>{children}</span>
return (
<Button
className="h-7 w-full truncate text-sm font-medium"
variant="ghost"
size="sm"
onClick={() => setNavView((prev) => (prev === "days" ? "years" : "days"))}
>
{navView === "days"
? children
: displayYears.from + " - " + displayYears.to}
</Button>
)
}
function MonthGrid({
className,
children,
displayYears,
startMonth,
endMonth,
navView,
setNavView,
...props
}: {
className?: string
children: React.ReactNode
displayYears: { from: number; to: number }
startMonth?: Date
endMonth?: Date
navView: NavView
setNavView: React.Dispatch<React.SetStateAction<NavView>>
} & React.TableHTMLAttributes<HTMLTableElement>) {
if (navView === "years") {
return (
<YearGrid
displayYears={displayYears}
startMonth={startMonth}
endMonth={endMonth}
setNavView={setNavView}
navView={navView}
className={className}
{...props}
/>
)
}
return (
<table className={className} {...props}>
{children}
</table>
)
}
function YearGrid({
className,
displayYears,
startMonth,
endMonth,
setNavView,
navView,
...props
}: {
className?: string
displayYears: { from: number; to: number }
startMonth?: Date
endMonth?: Date
setNavView: React.Dispatch<React.SetStateAction<NavView>>
navView: NavView
} & React.HTMLAttributes<HTMLDivElement>) {
const { goToMonth, selected } = useDayPicker()
return (
<div className={cn("grid grid-cols-4 gap-y-2", className)} {...props}>
{Array.from(
{ length: displayYears.to - displayYears.from + 1 },
(_, i) => {
const isBefore =
differenceInCalendarDays(
new Date(displayYears.from + i, 11, 31),
startMonth!
) < 0
const isAfter =
differenceInCalendarDays(
new Date(displayYears.from + i, 0, 0),
endMonth!
) > 0
const isDisabled = isBefore || isAfter
return (
<Button
key={i}
className={cn(
"h-7 w-full text-sm font-normal text-foreground",
displayYears.from + i === new Date().getFullYear() &&
"bg-accent font-medium text-accent-foreground"
)}
variant="ghost"
onClick={() => {
setNavView("days")
goToMonth(
new Date(
displayYears.from + i,
(selected as Date | undefined)?.getMonth() ?? 0
)
)
}}
disabled={navView === "years" ? isDisabled : undefined}
>
{displayYears.from + i}
</Button>
)
}
)}
</div>
)
}
export { Calendar }
src/registry/new-york/components/date-picker/date-picker.tsx
"use client"
import { Button } from "@/components/ui/button"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
import { cn } from "@/lib/utils"
import { Calendar } from "@/registry/calendar/calendar"
import { format } from "date-fns"
import { CalendarIcon } from "lucide-react"
import * as React from "react"
export default function DatePicker() {
const [date, setDate] = React.useState<Date>()
return (
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
className={cn(
"w-[240px] justify-start text-left font-normal",
!date && "text-muted-foreground"
)}
>
<CalendarIcon className="mr-2 h-4 w-4" />
{date ? format(date, "PPP") : <span>Pick a date</span>}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar mode="single" selected={date} onSelect={setDate} autoFocus />
</PopoverContent>
</Popover>
)
}
src/registry/new-york/components/date-picker/date-range-picker.tsx
"use client"
import { Button } from "@/components/ui/button"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
import { cn } from "@/lib/utils"
import { Calendar } from "@/registry/new-york/components/ui/calendar"
import { addDays, format } from "date-fns"
import { CalendarIcon } from "lucide-react"
import * as React from "react"
import { type DateRange } from "react-day-picker"
export default function DateRangePicker({
className,
}: React.HTMLAttributes<HTMLDivElement>) {
const [date, setDate] = React.useState<DateRange | undefined>({
from: addDays(new Date(), -20),
to: new Date(),
})
return (
<div className={cn("grid gap-2", className)}>
<Popover>
<PopoverTrigger asChild>
<Button
id="date"
variant="outline"
className={cn(
"w-[300px] justify-start text-left font-normal",
!date && "text-muted-foreground"
)}
>
<CalendarIcon className="mr-2 h-4 w-4" />
{date?.from ? (
date.to ? (
<>
{format(date.from, "LLL dd, y")} -{" "}
{format(date.to, "LLL dd, y")}
</>
) : (
format(date.from, "LLL dd, y")
)
) : (
<span>Pick a date</span>
)}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
autoFocus
mode="range"
defaultMonth={date?.from}
selected={date}
onSelect={setDate}
numberOfMonths={2}
/>
</PopoverContent>
</Popover>
</div>
)
}
src/components/ui/button.tsx
import { cn } from "@/lib/utils"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import * as React from "react"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-x-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }
src/lib/utils.ts
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}