react-day-picker v8 -> v9 Errors in the shadcn Calendar
A shadcn Calendar is a thin wrapper around react-day-picker. The
react-day-picker 9.0.0 release (mid-2024) reshaped almost every public
prop name, the classNames key system, the components-override surface,
and the formatter contract. Every v8 prop name still parses as valid
JSX in v9 (selectedDays, disabledDays, modifiers={[...]},
components={{ Caption }}), but the v9 DayPicker simply ignores them.
The symptom is therefore SILENT : the page compiles, the calendar
mounts, and nothing happens (no selection, no styles, no custom
caption). TypeScript narrows the typed errors to a single rule on
mode ; the rest manifests as visual breakage.
This skill maps each v8 syntax shape onto its v9 replacement, lists the
TypeScript error messages that surface for the typed cases, and
documents the canonical recovery flow when the shadcn-side
components/ui/calendar.tsx itself pre-dates the v9 release.
Companion skills :
shadcn-syntax-calendar-datepicker(Batch B7) : the v9 API surface in full and the DatePicker composition recipe (Popover + Calendar + Input). Read this skill FIRST for the green-field v9 syntax ; this errors skill assumes you already know the v9 shape and need to migrate v8 code or diagnose a v8-shape regression.shadcn-errors-cli-sync-mismatch: the canonical recovery flow usesnpx shadcn@latest add calendar --overwritewhen the shadcn-side calendar file is itself stale.shadcn-errors-styling-conflicts: when CSS classes look right but the calendar is still unstyled, thereact-day-picker/style.cssimport may have moved.
Quick Reference : v8 -> v9 Prop Rename Table
For every prop the calendar accepts, v9 either keeps the v8 name, renames it, or removes it entirely. This table is the single most important reference in this skill. ALWAYS consult it before changing any prop.
| v8 prop / API | v9 prop / API | Type / Notes |
|---|---|---|
selectedDays |
selected |
Date | Date[] | DateRange | undefined ; matches the mode |
disabledDays |
disabled |
Matcher | Matcher[] |
modifiers={[...]} |
modifiers={{ name: Matcher }} |
object of Matchers, NOT an array |
modifiersStyles |
modifiersStyles |
unchanged, but keys map to v9 modifier names |
modifiersClassNames |
modifiersClassNames |
unchanged, but keys map to v9 modifier names |
components.Caption |
components.MonthCaption |
the caption above the month grid |
components.IconLeft |
components.Chevron |
single component receives orientation: "left" | "right" | "up" | "down" |
components.IconRight |
components.Chevron |
same as above |
components.Row |
components.Week |
one row of days |
components.HeadRow |
components.Weekdays |
the header row with weekday names |
components.Day |
components.DayButton |
signature changed to ({ day, modifiers, ...props }) |
fromMonth |
startMonth |
Date |
toMonth |
endMonth |
Date |
fromDate |
startMonth + hidden matcher |
use hidden={{ before: Date }} to gray-out earlier days |
toDate |
endMonth + hidden matcher |
use hidden={{ after: Date }} |
month |
month |
unchanged (controlled current month) |
defaultMonth |
defaultMonth |
unchanged (uncontrolled initial month) |
numberOfMonths |
numberOfMonths |
unchanged |
mode IMPLICIT (selectedDays: Date) |
mode="single" | "multiple" | "range" REQUIRED |
v9 demands explicit mode when selected is set |
onSelect (per-mode) |
onSelect (per-mode) |
unchanged, but the value type is narrowed by mode |
classNames keyed by free strings (day, cell, caption) |
classNames keyed by UI enum strings (day, day_button, cell REMOVED, month_caption, range_start etc.) |
see UI enum in references/methods.md |
formatCaption: (m) => <strong>...</strong> |
formatCaption: (m) => "..." |
v9 formatters MUST return string, NOT JSX |
direct date-fns import inside DayPicker |
dateLib adapter prop |
pass a DateLib adapter so locales / time-zones flow through the picker |
react-day-picker/dist/style.css |
react-day-picker/style.css |
new import path, the old one 404s |
DayPickerSingleProps |
PropsSingle |
renamed type |
DayPickerMultipleProps |
PropsMulti |
renamed type |
DayPickerRangeProps |
PropsRange |
renamed type |
useNavigation |
useDayPicker |
renamed hook |
Verified at https://daypicker.dev/v9/upgrading (2026-05-19).
Quick Reference : The mode Prop is REQUIRED in v9
In v8, selectedDays={date} IMPLIED mode="single". v9 removes the
implicit form. The mode prop is now part of the union type that
narrows selected and onSelect :
// CORRECT v9 : single
<Calendar mode="single" selected={date} />
// CORRECT v9 : multiple
<Calendar mode="multiple" selected={dates} />
// CORRECT v9 : range
<Calendar mode="range" selected={range} />
// WRONG : mode missing, TypeScript error
<Calendar selected={date} />
// Type '{ selected: Date; onSelect: ... }' is not assignable to type
// 'DayPickerProps'. Property 'mode' is missing.
Without mode, v9 falls back to no selection at all : the picker
renders, dates are clickable, but the click never updates state.
TypeScript catches this at compile time IF strict: true is on.
Quick Reference : classNames is Keyed by the UI Enum
v9 publishes a UI string enum that names every visual slot in the
calendar (root, months, month, nav, button_previous, button_next,
month_caption, dropdowns, caption_label, table, weekdays, weekday,
week, day, day_button, range_start, range_middle, range_end, today,
outside, disabled, hidden, week_number, week_number_header, etc.).
The v8 keys (cell, day, caption, head_row, nav_button) either
moved (day is now the table cell, day_button is the button inside
it) or vanished entirely. Use getDefaultClassNames() and merge with
cn(...) so the shadcn defaults stay applied :
import { DayPicker, getDefaultClassNames } from "react-day-picker"
import { cn } from "@/lib/utils"
const defaultClassNames = getDefaultClassNames()
<DayPicker
classNames={{
root: cn("w-fit", defaultClassNames.root),
month_caption: cn("...your tailwind...", defaultClassNames.month_caption),
day: cn("...your tailwind...", defaultClassNames.day),
day_button: cn("...your tailwind...", defaultClassNames.day_button),
range_start: cn("rounded-l-md bg-accent", defaultClassNames.range_start),
// any v8 key (`cell`, `head_row`) is ignored
}}
/>
Verified at https://github.com/shadcn-ui/ui/blob/main/apps/v4/registry/new-york-v4/ui/calendar.tsx
(2026-05-19) : the shadcn Calendar source itself uses
getDefaultClassNames() and merges every v9 key.
Quick Reference : dateLib Adapter Replaces Direct date-fns
v8 imported date-fns directly inside DayPicker and accepted a
locale prop. v9 introduces a dateLib adapter so the calendar can
work with date-fns, dayjs, luxon, native Intl, or a custom
adapter. The shadcn Calendar still accepts locale (forwarded to
the default adapter), but for time-zones, custom week starts, or
non-Gregorian calendars the dateLib prop is the v9 surface :
import { DayPicker } from "react-day-picker"
import { fr } from "date-fns/locale"
// CORRECT v9 : locale only
<DayPicker locale={fr} />
// CORRECT v9 : dateLib with time-zone
<DayPicker dateLib={{ ...defaultDateLib, timeZone: "Europe/Amsterdam" }} />
// WRONG : v8 used `useNavigation` hook to read the locale ; v9 renamed it
import { useDayPicker } from "react-day-picker" // not useNavigation
See references/methods.md for the full DateLib signature.
Quick Reference : Symptoms-to-Fix Matrix
Match the symptom on the left to the exact v8 -> v9 cause on the right. ALWAYS run through this matrix BEFORE touching code.
| Symptom | Cause | Fix |
|---|---|---|
| Calendar renders but no day is highlighted as selected | selectedDays used instead of selected |
rename to selected, add mode="single" |
| Disabled days are still clickable | disabledDays used instead of disabled |
rename to disabled |
| Custom month-header component never appears | components.Caption used |
rename to components.MonthCaption |
| Left / right arrow icons are gone | components.IconLeft / IconRight used |
merge into components.Chevron with orientation switch |
TypeScript error : Property 'mode' is missing |
v9 demands mode |
add mode="single" | "multiple" | "range" |
TypeScript error : selectedDays does not exist on type DayPickerProps |
v8 prop name | rename to selected |
| Calendar is unstyled, looks like a raw HTML table | react-day-picker/dist/style.css import 404s on v9, OR classNames uses v8 keys |
change import to react-day-picker/style.css AND rekey to v9 UI strings |
modifiers.weekend not applied to any day |
modifiers={[{...}]} array form |
switch to object form modifiers={{ weekend: Matcher }} |
Custom <Day> renderer errors with "Cannot read property of undefined" |
v8 Day signature | switch to components.DayButton with ({ day, modifiers, ...props }) |
Range selection ignores fromMonth / toMonth boundaries |
v8 boundary props | rename to startMonth / endMonth |
formatCaption throws "Objects are not valid as a React child" |
v9 formatters must return string |
return a string ; for JSX use components.MonthCaption |
Import error : Module not found: react-day-picker/dist/style.css |
v9 moved the CSS | use react-day-picker/style.css |
useNavigation is not a function |
v9 renamed the hook | use useDayPicker |
Quick Reference : Canonical Recovery Flow
If your shadcn-side components/ui/calendar.tsx PRE-dates the v9 release
(check : does it import getDefaultClassNames ? does it set
mode="single" defaults ? does it map Chevron ?), the file itself is
v8-shape. Manual patching is fragile. The supported recovery is to
re-run the shadcn CLI :
npx shadcn@latest diff calendar # preview the v9 calendar source
npx shadcn@latest add calendar --overwrite # rewrite to v9 shape
This regenerates components/ui/calendar.tsx with the v9
getDefaultClassNames() / components.Chevron / components.DayButton
wiring. After overwrite, any consumer site that still passes v8 props
through Calendar will surface TypeScript errors at the consumer site,
which you fix using the rename table above.
Verified at https://github.com/shadcn-ui/ui/issues/4366 (2026-05-19,
97 reactions, open) : the canonical maintainer-recommended fix is to
re-run shadcn add calendar --overwrite.
Decision Tree : Diagnosing a Broken Calendar After an Upgrade
START : Calendar broken after `react-day-picker@9` install
|
+-- Does TypeScript compile ?
| +-- NO : read the error
| | +-- "Property 'mode' is missing" -> add `mode="single|multiple|range"`
| | +-- "selectedDays does not exist" -> rename to `selected`
| | +-- "disabledDays does not exist" -> rename to `disabled`
| | +-- "DayPickerSingleProps does not exist" -> rename to `PropsSingle`
| +-- YES : continue
|
+-- Does Calendar render at all ?
| +-- NO : check the CSS import path (must be `react-day-picker/style.css`)
| +-- YES : continue
|
+-- Are styles applied ?
| +-- NO : `classNames` still keyed by v8 strings ; rekey using `UI` enum
| +-- YES : continue
|
+-- Does selection update on click ?
| +-- NO : `mode` missing ; selection is silently disabled
| +-- YES : continue
|
+-- Are custom captions / icons appearing ?
| +-- NO : `components.Caption` / `IconLeft` / `IconRight` ignored ; rename to MonthCaption / Chevron
| +-- YES : DONE
Cross-File Companion : When To Re-Add Calendar
If MORE THAN 3 v8 prop names appear in the project AND the
components/ui/calendar.tsx file does NOT contain
getDefaultClassNames, do NOT patch by hand. Run :
npx shadcn@latest add calendar --overwrite
Then fix the consumer call sites with the rename table. Patching a
v8-shape calendar.tsx to v9 by hand creates a long tail of subtle
bugs (classNames keys, components signatures, formatter contracts)
that the CLI rewrite resolves in one shot.
Reference Files
references/methods.md: full v8 -> v9 API rename table,UIenum reference,DateLibadapter signature,Matchertype, components override map, formatter contract.references/examples.md: v8 single-date code -> v9 equivalent ; v8 range -> v9 withDateRange; v8disabledDays-> v9disabled; v8 customCaption-> v9MonthCaption; v8modifiersarray -> v9modifiersobject of Matchers ;dateLibwith a custom locale.references/anti-patterns.md: six recurring v8-shape-in-v9 failure modes with symptom, root cause, and verified fix.
Sources
- https://daypicker.dev/v9/upgrading (v9 official upgrade guide, last verified 2026-05-19)
- https://daypicker.dev/v9/guides/custom-modifiers (verified 2026-05-19)
- https://github.com/shadcn-ui/ui/blob/main/apps/v4/registry/new-york-v4/ui/calendar.tsx (shadcn Calendar v9-compatible source, verified 2026-05-19)
- https://github.com/shadcn-ui/ui/issues/4366 (canonical regression issue, 97 reactions, verified 2026-05-19)
- https://ui.shadcn.com/docs/components/calendar (shadcn Calendar docs, verified 2026-05-19)