Use when adding, customising, or debugging a shadcn ui Calendar or Date Picker, choosing between the three react-day-picker v9 selection modes (single / multiple / range), typing the `selected` state and the `onSelect` callback for each mode (Date vs Date[] vs DateRange), wiring a DatePicker by composing Calendar inside a Popover with a Button trigger that formats the date via date-fns, localising the calendar to Dutch / German / French via the `dateLib` adapter, disabling specific dates with a `disabled` matcher or a date-fns predicate, styling the calendar via `classNames` + `modifiers` + `modifiersClassNames`, swapping the day renderer through the `components` prop, or migrating an existing Calendar file from react-day-picker v8 to v9 after running `shadcn add calendar --overwrite`. Prevents the canonical Calendar failures : omitting the required `mode` prop so TypeScript narrows `selected` to `undefined` and the calendar never marks any day as selected, importing v8-only props (`selectedDays`, `disabledDa
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
Use when adding, customising, or debugging a shadcn ui Calendar or Date Picker, choosing between the three react-day-picker v9 selection modes (single / multiple / range), typing the `selected` state and the `onSelect` callback for each mode (Date vs Date[] vs DateRange), wiring a DatePicker by composing Calendar inside a Popover with a Button trigger that formats the date via date-fns, localising the calendar to Dutch / German / French via the `dateLib` adapter, disabling specific dates with a `disabled` matcher or a date-fns predicate, styling the calendar via `classNames` + `modifiers` + `modifiersClassNames`, swapping the day renderer through the `components` prop, or migrating an existing Calendar file from react-day-picker v8 to v9 after running `shadcn add calendar --overwrite`. Prevents the canonical Calendar failures : omitting the required `mode` prop so TypeScript narrows `selected` to `undefined` and the calendar never marks any day as selected, importing v8-only props (`selectedDays`, `disabledDays`, `fromDate`, `toDate`, `fromMonth`, `toMonth`, `numberOfMonths` typing) that no longer exist on the v9 surface, declaring `useState<Date>()` and then passing the setter to `mode="range"` which expects `DateRange | undefined`, forgetting `"use client"` at the top of any file that mounts a Calendar so the `Intl.DateTimeFormat()` call inside react-day-picker triggers a hydration mismatch in Next.js App Router, hand-styling individual day cells with arbitrary Tailwind instead of routing through `classNames.day` / `modifiers` so future `shadcn add calendar --overwrite` runs nuke the customisation, and treating DatePicker as a single importable primitive when it is a documented Popover + Button + Calendar recipe with no `<DatePicker />` export. Covers the v9 Mode union (`"single" | "multiple" | "range"`), exact selected/onSelect type per mode, the DateRange type (`{ from: Date | undefined; to?: Date | undefined }`), the v8 -> v9 prop renames (`fromMonth`/`fromYear` -> `startMonth`, `toMonth`/`toYear` -> `endMonth`, `fromDate`/`toDate` -> `hidden` + before/after matcher, `Caption` -> `MonthCaption`, `Row` -> `Week`, `HeadRow` -> `Weekdays`, `IconLeft`/`IconRight` -> single `Chevron`, `day_selected` -> `selected`, `day_disabled` -> `disabled`), the shadcn Calendar wrapper props (`buttonVariant`, `captionLayout`, `showOutsideDays`, `formatters`, `components`), the locked-down `classNames` keys exposed by `getDefaultClassNames()`, the Popover + Button DatePicker recipe for single / range / preset variants, the date-fns `format(..., "PPP")` and `format(..., "LLL dd, y")` trigger-label patterns, locale wiring via the v9 dateLib adapter and `date-fns/locale/nl`, RSC safety (`"use client"` mandatory), and the forward pointer to `shadcn-errors-react-day-picker-v9` for recovery from the v8 -> v9 silent breakage. Keywords: shadcn calendar, shadcn date picker, datepicker, Calendar component, DatePicker recipe, react-day-picker v9, react-day-picker v8 to v9, DayPicker, mode single, mode multiple, mode range, DateRange type, onSelect callback, selected Date undefined, calendar disabled dates, fromDate toDate, startMonth endMonth, captionLayout dropdown, dateLib adapter, locale nl date-fns, getDefaultClassNames, classNames day selected, modifiers modifiersClassNames, CalendarDayButton, components prop, custom day renderer, Chevron orientation, MonthCaption, Popover Calendar Button, asChild PopoverTrigger, format PPP, format LLL, Pick a date placeholder, numberOfMonths range, defaultMonth, showOutsideDays, hydration mismatch Intl DateTimeFormat, use client Calendar, shadcn add calendar overwrite, calendar v9 breaking change, selectedDays renamed, fromDate hidden before after matcher, how do I make a date picker, how do I disable past dates, how do I localize the calendar, calendar nothing selected, range from to optional, DateRange from undefined.
license
MIT
compatibility
Designed for Claude Code. Requires shadcn ui evergreen-2026 with react-day-picker v9 (BREAKING change from v8). For v8 -> v9 recovery, see shadcn-errors-react-day-picker-v9.
metadata
{"author":"OpenAEC-Foundation","version":"1.0"}
shadcn ui : Calendar + DatePicker Syntax
The shadcn Calendar is a thin wrapper over react-day-picker v9 (DayPicker re-exported as Calendar). The DatePicker is NOT a primitive : it is a documented composition of Popover + Button (as PopoverTrigger asChild) + Calendar (inside PopoverContent). Every other "date input" pattern (preset shortcuts, range pickers, date+time hybrids) derives from this recipe.
The single most common failure on this primitive is the silent v8 -> v9 break : the API was redesigned mid-2025 (issue #4366, 97 reactions). Old Calendar source files that pre-date shadcn v4.x will silently misbehave on react-day-picker@9.0.0. Recovery is shadcn add calendar --overwrite. See Companion : shadcn-errors-react-day-picker-v9 for the full migration playbook.
mode is mandatory in v9 : without it, TypeScript narrows selected to undefined and no day is ever marked selected. "use client" is non-optional : Calendar calls Intl.DateTimeFormat() internally and will cause a Next.js App Router hydration mismatch if rendered as an RSC.
Mode decision table
User intent
mode
selected type
onSelect signature
Pick exactly one day
"single"
Date | undefined
(date: Date | undefined) => void
Pick any number of days
"multiple"
Date[] | undefined
(dates: Date[] | undefined) => void
Pick a start + end range
"range"
DateRange | undefined
(range: DateRange | undefined) => void
DateRange is { from: Date \| undefined; to?: Date \| undefined }. ALWAYS read from before to ; while the user is mid-drag, to is undefined. NEVER assume both ends are present.
If required: true is also passed, the corresponding undefined branch is removed from the type (single becomes Date, multiple becomes Date[], range becomes DateRange). NEVER mix required: true with useState<Date | undefined>() ; the setter will type-error.
Four invariants
ALWAYS pass mode explicitly. NEVER rely on a "default" mode ; v9 has no default and TypeScript will narrow selected to undefined silently if mode is omitted.
ALWAYS prefix the file with "use client". NEVER mount Calendar in a Server Component ; Intl.DateTimeFormat() runs in the renderer and causes a hydration mismatch.
ALWAYS type the state to match the mode (useState<Date | undefined>() for single, useState<Date[] | undefined>() for multiple, useState<DateRange | undefined>() for range). NEVER reuse a Date setter for mode="range".
ALWAYS treat the DatePicker as a recipe, NOT a component. NEVER write import { DatePicker } from "@/components/ui/date-picker" ; there is no such export. Compose Popover + Button (asChild on PopoverTrigger) + Calendar.
Decision Tree 1 : Which mode?
Q1. Does the user pick a single calendar day (birthday, deadline,
appointment)?
yes -> mode="single" + useState<Date | undefined>()
no -> Q2
Q2. Does the user pick zero or more days, with no implied order
(multi-day absence form, "available days" booking)?
yes -> mode="multiple" + useState<Date[] | undefined>()
no -> Q3
Q3. Does the user pick a continuous span from a start day to an end
day (holiday request, report period filter, hotel booking)?
yes -> mode="range" + useState<DateRange | undefined>()
+ ALWAYS render Calendar with numberOfMonths={2} for the
two-month side-by-side range UX
no -> reconsider : single, multiple, or range covers every
react-day-picker v9 selection. There is no other mode.
Decision Tree 2 : Calendar standalone or DatePicker recipe?
Q1. Should the calendar be visible at all times (admin grid, booking
page main surface, dashboard calendar)?
yes -> render <Calendar /> directly ; do NOT wrap in Popover
no -> Q2
Q2. Should the calendar appear only when the user opens an input
field or a button (form field, filter trigger, header date
picker)?
yes -> DatePicker recipe : Popover + Button (asChild on
PopoverTrigger) + Calendar inside PopoverContent ;
format the selected date in the Button label via
date-fns format()
no -> reconsider : either the calendar is always visible (Q1)
or it is gated by a trigger (Q2). There is no third
presentation.
Decision Tree 3 : Style override : className vs classNames vs modifiers?
Q1. Are you styling the OUTER calendar wrapper (border, padding,
background)?
yes -> className="..." on <Calendar /> ; merges via cn() in
the shadcn wrapper
no -> Q2
Q2. Are you restyling a structural slot of the day grid (the row of
weekday labels, the previous/next nav buttons, the day cell
itself, the dropdown caption)?
yes -> classNames={{ slot_key: "tailwind classes" }} ; valid
keys come from getDefaultClassNames() and include root,
months, month, nav, button_previous, button_next,
month_caption, dropdowns, dropdown_root, caption_label,
weekdays, weekday, week, day, range_start, range_middle,
range_end, today, outside, disabled, hidden
no -> Q3
Q3. Are you flagging SOME days as semantically special (holidays,
booked, available, today's-deadline) and want to style THOSE
specifically?
yes -> modifiers={{ holiday: holidayDates }} +
modifiersClassNames={{ holiday: "bg-red-100" }} ; the
wrapper auto-applies the className whenever a day
matches the modifier predicate
no -> Q4
Q4. Are you replacing the rendered DOM for a slot (custom day
button, custom chevron icon, custom dropdown)?
yes -> components={{ DayButton: MyDayButton, Chevron: MyIcon }}
no -> reconsider : every Calendar customisation maps to one
of className / classNames / modifiers / components.
NEVER reach into Calendar.tsx with patches.
ALWAYS type the state as Date | undefined ; the onSelect callback receives undefined when the user clicks the currently-selected day a second time (deselect).
Pattern 2 : Range Calendar with two-month side-by-side
DateRange is imported from react-day-picker, NOT from @/components/ui/calendar ; the shadcn wrapper does not re-export it. numberOfMonths={2} is the canonical range UX.
ALWAYS use asChild on PopoverTrigger so the Button (not a wrapper <div>) becomes the accessible trigger. NEVER nest a Button inside a Button. ALWAYS render the selected date via format(date, "PPP") (date-fns localised long form) ; raw date.toString() leaks the GMT offset into the label.
Pattern 4 : DatePicker range recipe with two-month popover
ALWAYS branch on date?.from first, THEN on date.to, because mid-drag the user has chosen the start but not yet the end. NEVER assume both ends are populated when rendering the label.
import { isBefore, startOfToday, isWeekend } from"date-fns"// Disable everything before today (no past dates)
<Calendar
mode="single"
selected={date}
onSelect={setDate}
disabled={(d) =>isBefore(d, startOfToday())}
/>
// Disable weekends<Calendarmode="single"selected={date}onSelect={setDate}disabled={isWeekend}
/>// Disable a fixed array<Calendarmode="single"selected={date}onSelect={setDate}disabled={[newDate(2026, 11, 25), newDate(2026, 0, 1)]}
/>
disabled accepts a Date, Date[], a (date: Date) => boolean predicate, OR any of the v9 matcher objects ({ before: Date }, { after: Date }, { from: Date; to: Date }, { dayOfWeek: number[] }). NEVER use the v8 prop name disabledDays.
Pattern 6 : Locale via date-fns adapter
The shadcn wrapper does not expose a locale prop directly ; locale flows through react-day-picker's built-in date-fns adapter via the locale prop, which is passed through ...props :
For non-Gregorian calendars (Persian, Hijri), import from the dedicated entry points (react-day-picker/persian, react-day-picker/hijri) ; do NOT pass them through locale. See methods.md for the full dateLib adapter signature.
Pattern 7 : Modifiers + modifiersClassNames (semantic day styling)
ALWAYS prefer modifiers + modifiersClassNames for semantic-day styling. NEVER patch individual day cells by hand : the next shadcn add calendar --overwrite will erase the patch.
v8 -> v9 migration callout
If the project's components/ui/calendar.tsx was generated before mid-2025 it predates react-day-picker v9 and will silently break after a pnpm update react-day-picker. The fastest recovery is :
pnpm dlx shadcn@latest add calendar --overwrite
This re-pulls the v9-compatible source from the registry. The v8 -> v9 prop renames you will see in old code :
v8 prop / name
v9 replacement
fromMonth / fromYear
startMonth
toMonth / toYear
endMonth
fromDate / toDate
hidden with { before } / { after } matcher
selectedDays
selected
disabledDays
disabled
Caption (component)
MonthCaption
Row (component)
Week
HeadRow (component)
Weekdays
IconLeft + IconRight
single Chevron with orientation
day_selected (className key)
selected
day_disabled (className key)
disabled
cell (className key)
day
day (className key)
day_button
Full migration table and recovery playbook : shadcn-errors-react-day-picker-v9.
Reference Links
methods.md : Full prop signatures per mode, DateRange type, dateLib adapter, modifiers + classNames keys
examples.md : Single / multiple / range Calendar, DatePicker recipe with PopoverButton trigger, Dutch locale via date-fns/locale/nl, disabled-dates with date-fns predicates
anti-patterns.md : Five Calendar-level anti-patterns and their recoveries
Companion Skills
shadcn-syntax-popover-tooltip-hovercard : DatePicker recipe relies on Popover + PopoverTrigger + PopoverContent ; consult for align, side, controlled open state, and Portal stacking concerns.
shadcn-syntax-button : DatePicker trigger is a <Button variant="outline"> rendered via PopoverTrigger asChild ; consult for asChild Slot rules and the variant catalogue.