Repository: gpbl/react-day-picker
Pull request #2211 (2024-06-19 16:02:48)

refactor: simplify props, contexts

github-actions[bot]: ## Pull request title linting :rotating_light:

In order to merge this pull request, the title of the pull request should be prefixed by one of the available types.

### Available types:

- **feat**: A new feature
- **fix**: A bug fix
- **docs**: Documentation only changes
- **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
- **refactor**: A code change that neither fixes a bug nor adds a feature
- **perf**: A code change that improves performance
- **test**: Adding missing tests or correcting existing tests
- **build**: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
- **ci**: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
- **chore**: Other changes that don't modify src or test files
- **revert**: Reverts a previous commit

---

<details>
<summary>Examples</summary>

```
feat(grpc): add new endpoint
refactor: combine class A and class B
ci: update pull request linter
style: change format of strings
```

</details>

:tipping_hand_person: For more examples, visit https://www.conventionalcommits.org/en/v1.0.0/#examples.

Patch:
diff --git a/examples/CustomCaption.tsx b/examples/CustomCaption.tsx
index fe0d9c27e..1df5b6d71 100644
--- a/examples/CustomCaption.tsx
+++ b/examples/CustomCaption.tsx
@@ -1,10 +1,14 @@
 import React from "react";
 
 import { format } from "date-fns";
-import { MonthCaptionProps, DayPicker, useCalendar } from "react-day-picker";
+import {
+  MonthCaptionProps,
+  DayPicker,
+  useCalendarContext
+} from "react-day-picker";
 
 function CustomMonthCaption(props: MonthCaptionProps) {
-  const { goToMonth, nextMonth, previousMonth } = useCalendar();
+  const { goToMonth, nextMonth, previousMonth } = useCalendarContext();
   return (
     <h2>
       {format(props.month.date, "MMM yyy")}
diff --git a/examples/CustomMultiple.tsx b/examples/CustomMultiple.tsx
index 46083001e..9568c26b6 100644
--- a/examples/CustomMultiple.tsx
+++ b/examples/CustomMultiple.tsx
@@ -1,12 +1,15 @@
 import React, { useState } from "react";
 
 import { isSameDay } from "date-fns";
-import { DayMouseEventHandler, DayPicker } from "react-day-picker";
+import { DayEventHandler, DayPicker } from "react-day-picker";
 
 export function CustomMultiple() {
   const [value, setValue] = useState<Date[]>([]);
 
-  const handleDayClick: DayMouseEventHandler = (day, modifiers) => {
+  const handleDayClick: DayEventHandler<React.MouseEvent> = (
+    day,
+    modifiers
+  ) => {
     const newValue = [...value];
     if (modifiers.selected) {
       const index = value.findIndex((d) => isSameDay(day, d));
diff --git a/examples/CustomSingle.tsx b/examples/CustomSingle.tsx
index e31a6dbde..a13504d74 100644
--- a/examples/CustomSingle.tsx
+++ b/examples/CustomSingle.tsx
@@ -4,7 +4,7 @@ import { DayPicker, DayPickerProps, type Mode } from "react-day-picker";
 
 export function CustomSingle() {
   const [selectedDate, setSelectedDate] = useState<Date | undefined>();
-  const modifiers: DayPickerProps<Mode>["modifiers"] = {};
+  const modifiers: DayPickerProps["modifiers"] = {};
   if (selectedDate) {
     modifiers.selected = selectedDate;
   }
diff --git a/examples/Dialog.tsx b/examples/Dialog.tsx
index a6004e56a..a535e965e 100644
--- a/examples/Dialog.tsx
+++ b/examples/Dialog.tsx
@@ -1,7 +1,7 @@
 import React, { useEffect, useId, useRef, useState } from "react";
 
 import { format, isValid, parse } from "date-fns";
-import { DayPicker, type SelectHandler } from "react-day-picker";
+import { DayPicker } from "react-day-picker";
 
 export function Dialog() {
   const dialogRef = useRef<HTMLDialogElement>(null);
@@ -45,7 +45,7 @@ export function Dialog() {
    * Function to handle the DayPicker select event: update the input value and
    * the selected date, and set the month.
    */
-  const handleDayPickerSelect: SelectHandler<"single"> = (date) => {
+  const handleDayPickerSelect = (date: Date | undefined) => {
     if (!date) {
       setInputValue("");
       setSelectedDate(undefined);
diff --git a/examples/Keyboard.tsx b/examples/Keyboard.tsx
index d57887215..36ae98ab7 100644
--- a/examples/Keyboard.tsx
+++ b/examples/Keyboard.tsx
@@ -1,8 +1,12 @@
 import React, { useState } from "react";
 
-import { DayPicker, type DayPickerProps } from "react-day-picker";
+import {
+  DayPicker,
+  type DayPickerProps,
+  type PropsSingle
+} from "react-day-picker";
 
-export function Keyboard(props: DayPickerProps<"single">) {
+export function Keyboard(props: DayPickerProps & PropsSingle) {
   const [selected, setSelected] = useState<Date | undefined>(undefined);
   return (
     <DayPicker
diff --git a/examples/MultipleMinMax.tsx b/examples/MultipleMinMax.tsx
index 3dc36ca63..7e646cc1a 100644
--- a/examples/MultipleMinMax.tsx
+++ b/examples/MultipleMinMax.tsx
@@ -4,13 +4,6 @@ import { addDays } from "date-fns";
 import { DayPicker } from "react-day-picker";
 
 export function MultipleMinMax() {
-  const defaultSelected = [new Date(), addDays(new Date(), 1)];
-  return (
-    <DayPicker
-      defaultSelected={defaultSelected}
-      mode="multiple"
-      min={2}
-      max={5}
-    />
-  );
+  const selected = [new Date(), addDays(new Date(), 1)];
+  return <DayPicker selected={selected} mode="multiple" min={2} max={5} />;
 }
diff --git a/examples/None.tsx b/examples/None.tsx
index c515580c0..ec60f8c7b 100644
--- a/examples/None.tsx
+++ b/examples/None.tsx
@@ -3,5 +3,5 @@ import React from "react";
 import { DayPicker } from "react-day-picker";
 
 export function None() {
-  return <DayPicker mode="default" />;
+  return <DayPicker />;
 }
diff --git a/examples/RangeShiftKey.tsx b/examples/RangeShiftKey.tsx
index dcec46679..7c381a357 100644
--- a/examples/RangeShiftKey.tsx
+++ b/examples/RangeShiftKey.tsx
@@ -4,12 +4,12 @@ import { isSameDay } from "date-fns";
 import {
   DateRange,
   DayPicker,
-  type DayProps,
-  useSelection
+  useRangeContext,
+  type DayProps
 } from "react-day-picker";
 
 function DayWithShiftKey(props: DayProps) {
-  const { selected } = useSelection<"range">();
+  const { selected } = useRangeContext();
   const onClick = props.rootProps?.onClick;
 
   const handleClick: MouseEventHandler<HTMLDivElement> = (e) => {
diff --git a/react-day-picker.code-workspace b/react-day-picker.code-workspace
index c5cc442ef..647f6e3f5 100644
--- a/react-day-picker.code-workspace
+++ b/react-day-picker.code-workspace
@@ -34,6 +34,16 @@
       "**/node_modules": true,
       "**/versioned_docs": true
     },
-    "jest.runMode": "on-save"
+    "jest.runMode": "on-demand",
+    "git.showProgress": true,
+    "scm.diffDecorationsGutterVisibility": "hover",
+    "scm.diffDecorationsGutterPattern": {
+      "modified": false
+    },
+    "scm.diffDecorationsGutterWidth": 1,
+    "scm.diffDecorations": "none",
+    "[typescriptreact]": {
+      "editor.defaultFormatter": "esbenp.prettier-vscode"
+    }
   }
 }
diff --git a/src/DayPicker.tsx b/src/DayPicker.tsx
index 2d3dcd12d..0119a6d05 100644
--- a/src/DayPicker.tsx
+++ b/src/DayPicker.tsx
@@ -1,22 +1,17 @@
 import React from "react";
 
 import { Calendar } from "./components/Calendar";
-import { ContextProviders } from "./contexts/root";
-import type { DayPickerProps, Mode } from "./types";
+import { ContextProviders } from "./contexts/providers";
+import type { DayPickerProps } from "./types";
 
 /**
  * DayPicker is a React component to create date pickers, calendars, and date
  * inputs for web applications.
  *
- * @template T - The {@link Mode | selection mode}. Defaults to `"default"`.
- * @template R - Whether the selection is required. Defaults to `false`.
  * @group Components
  * @see http://daypicker.dev
  */
-export function DayPicker<
-  T extends Mode = "default",
-  R extends boolean = false
->(props: DayPickerProps<T, R>) {
+export function DayPicker(props: DayPickerProps) {
   return (
     <ContextProviders {...props}>
       <Calendar />
diff --git a/src/UI.ts b/src/UI.ts
index c46f37113..f8705cf4c 100644
--- a/src/UI.ts
+++ b/src/UI.ts
@@ -4,6 +4,8 @@ import type { CustomComponents, ClassNames, Styles } from "./types";
  * The UI elements composing DayPicker. These elements are mapped to
  * {@link CustomComponents}, the {@link ClassNames} and the {@link Styles} used by
  * DayPicker.
+ *
+ * Some of these elements are extended by flags and modifiers.
  */
 export enum UI {
   /** The previous button in the navigation. */
@@ -17,7 +19,10 @@ export enum UI {
   Calendar = "calendar",
   /** The Chevron SVG element used by navigation buttons and dropdowns. */
   Chevron = "chevron",
-  /** The grid cell with the day's date. Extended by {@link DayModifier}. */
+  /**
+   * The grid cell with the day's date. Extended by {@link DayFlag} and
+   * {@link SelectionFlag}.
+   */
   Day = "day",
   /** The element containing the formatted day's date, inside the grid cell. */
   DayDate = "day_date",
@@ -60,14 +65,27 @@ export enum UI {
   YearsDropdown = "years_dropdown"
 }
 
-/** The modifiers for the {@link UI.Day}. */
-export enum DayModifier {
+/** The flags for the {@link UI.Day}. */
+export enum DayFlag {
   /** The day is disabled */
   disabled = "disabled",
   /** The day is hidden */
   hidden = "hidden",
   /** The day is outside the current month */
   outside = "outside",
+  /** The day is focusable. */
+  focusable = "focusable",
+  /** The day is focused. */
+  focused = "focused",
+  /** The day is today. */
+  today = "today"
+}
+
+/**
+ * The state that can be applied to the {@link UI.Day} element when in selection
+ * mode.
+ */
+export enum SelectionState {
   /** The day is at the end of a selected range. */
   range_end = "range_end",
   /** The day is at the middle of a selected range. */
@@ -75,23 +93,17 @@ export enum DayModifier {
   /** The day is at the start of a selected range. */
   range_start = "range_start",
   /** The day is selected. */
-  selected = "selected",
-  /** The day is focusable. */
-  focusable = "focusable",
-  /** The day is focused. */
-  focused = "focused",
-  /** The day is today. */
-  today = "today"
+  selected = "selected"
 }
 
 /** Flags that can be applied to the {@link UI.Calendar} element. */
 export enum CalendarFlag {
   /** Assigned when the week numbers are show. */
-  hasWeekNumbers = "has_week_numbers",
+  has_week_numbers = "has_week_numbers",
   /** Assigned when the weekdays are hidden. */
-  noWeekdays = "no_weekdays",
+  no_weekdays = "no_weekdays",
   /** Assigned when the calendar has multiple months. */
-  hasMultipleMonths = "has_multiple_months"
+  has_multiple_months = "has_multiple_months"
 }
 
 /** Flags that can be applied to the {@link UI.Chevron} element. */
@@ -106,5 +118,5 @@ export enum WeekNumberFlag {
    * Assigned when the week number is interactive, i.e. has an
    * `onWeekNumberClick` event attached to it.
    */
-  isInteractive = "week_number_interactive"
+  week_number_interactive = "week_number_interactive"
 }
diff --git a/src/components/Button.tsx b/src/components/Button.tsx
index 22bb29032..2e4805c1a 100644
--- a/src/components/Button.tsx
+++ b/src/components/Button.tsx
@@ -1,7 +1,4 @@
-import React, { ButtonHTMLAttributes } from "react";
-
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-import type { PropsBase } from "../types";
+import React, { type ButtonHTMLAttributes } from "react";
 
 /**
  * Render the button elements in the calendar.
diff --git a/src/components/Calendar.tsx b/src/components/Calendar.tsx
index 9ae962609..48b25562d 100644
--- a/src/components/Calendar.tsx
+++ b/src/components/Calendar.tsx
@@ -1,8 +1,8 @@
 import React from "react";
 
 import { UI, CalendarFlag } from "../UI";
-import { useCalendar } from "../contexts/calendar";
-import { useProps } from "../contexts/props";
+import { useCalendarContext } from "../contexts/calendar";
+import { usePropsContext } from "../contexts/props";
 
 import { Footer as DefaultFooter } from "./Footer";
 import { Month as DefaultMonth } from "./Month";
@@ -35,9 +35,9 @@ export function Calendar() {
     style,
     styles,
     title
-  } = useProps();
+  } = usePropsContext();
 
-  const calendar = useCalendar();
+  const calendar = useCalendarContext();
 
   // Apply classnames according to props
   const cssClassNames = [classNames[UI.Calendar]];
@@ -45,13 +45,13 @@ export function Calendar() {
     cssClassNames.push(className);
   }
   if (numberOfMonths > 1) {
-    cssClassNames.push(classNames[CalendarFlag.hasMultipleMonths]);
+    cssClassNames.push(classNames[CalendarFlag.has_multiple_months]);
   }
   if (showWeekNumber) {
-    cssClassNames.push(classNames[CalendarFlag.hasWeekNumbers]);
+    cssClassNames.push(classNames[CalendarFlag.has_week_numbers]);
   }
   if (hideWeekdayRow) {
-    cssClassNames.push(classNames[CalendarFlag.noWeekdays]);
+    cssClassNames.push(classNames[CalendarFlag.no_weekdays]);
   }
 
   const Nav = components?.Nav ?? DefaultNav;
diff --git a/src/components/Chevron.tsx b/src/components/Chevron.tsx
index 372882124..0abd469a2 100644
--- a/src/components/Chevron.tsx
+++ b/src/components/Chevron.tsx
@@ -1,7 +1,7 @@
 import React from "react";
 
 import { ChevronFlag, UI } from "../UI";
-import { useProps } from "../contexts/props";
+import { usePropsContext } from "../contexts/props";
 
 /**
  * Render the chevron icon used in the navigation buttons and dropdowns.
@@ -16,7 +16,7 @@ export function Chevron(props: {
   orientation?: "up" | "down" | "left" | "right";
 }) {
   const { size = 24, orientation = "left" } = props;
-  const { classNames, disableNavigation } = useProps();
+  const { classNames, disableNavigation } = usePropsContext();
 
   const svgClassName = [
     classNames[UI.Chevron],
diff --git a/src/components/Day.tsx b/src/components/Day.tsx
index a99d2eef2..cba36e61d 100644
--- a/src/components/Day.tsx
+++ b/src/components/Day.tsx
@@ -1,8 +1,8 @@
 import React from "react";
-import type { HTMLAttributes, ReactNode } from "react";
+import type { ReactNode } from "react";
 
 import type { CalendarDay } from "../classes";
-import type { DayModifiers } from "../types";
+import type { Modifiers } from "../types";
 
 /**
  * Render the gridcell of a day in the calendar and handle the interaction and
@@ -17,7 +17,7 @@ import type { DayModifiers } from "../types";
  */
 export function Day(props: {
   day: CalendarDay;
-  modifiers: DayModifiers;
+  modifiers: Modifiers;
   children?: ReactNode;
   rootProps: Pick<
     JSX.IntrinsicElements["div"],
diff --git a/src/components/DayDate.tsx b/src/components/DayDate.tsx
index 28c0b1605..e7275b3f2 100644
--- a/src/components/DayDate.tsx
+++ b/src/components/DayDate.tsx
@@ -1,7 +1,7 @@
 import React from "react";
 
 import type { CalendarDay } from "../classes";
-import type { DayModifiers } from "../types";
+import type { Modifiers } from "../types";
 
 /**
  * Render the date as string inside the day grid cell.
@@ -16,7 +16,7 @@ export function DayDate(props: {
   /** The date to display. */
   formattedDate: string;
   /** The modifiers for the day. */
-  modifiers: DayModifiers;
+  modifiers: Modifiers;
   /** The HTML attributes for the root element. */
   rootProps: {
     className: string;
@@ -27,3 +27,14 @@ export function DayDate(props: {
 }
 
 export type DayDateProps = Parameters<typeof DayDate>[0];
+
+/**
+ * @deprecated The component has been renamed. Use `DayDate` instead.
+ * @protected
+ */
+export const DayContent = DayDate;
+/**
+ * @deprecated The type has been renamed. Use `DayDateProps` instead.
+ * @protected
+ */
+export type DayContentProps = DayDateProps;
diff --git a/src/components/DayWrapper.tsx b/src/components/DayWrapper.tsx
index 222601c16..d6c571e6b 100644
--- a/src/components/DayWrapper.tsx
+++ b/src/components/DayWrapper.tsx
@@ -1,24 +1,17 @@
 import React from "react";
-import {
-  type FocusEventHandler,
-  type KeyboardEventHandler,
-  type MouseEventHandler,
-  type PointerEventHandler,
-  type TouchEventHandler,
-  useEffect,
-  useRef
-} from "react";
 
-import { UI, DayModifier } from "../UI";
+import { UI, DayFlag } from "../UI";
 import { CalendarDay } from "../classes/CalendarDay";
-import { useCalendar } from "../contexts/calendar";
-import { useFocus } from "../contexts/focus";
-import { useModifiers } from "../contexts/modifiers";
-import { useProps } from "../contexts/props";
-import { useSelection } from "../contexts/selection";
+import { useCalendarContext } from "../contexts/calendar";
+import { useFocusContext } from "../contexts/focus";
+import { useModifiersContext } from "../contexts/modifiers";
+import { usePropsContext } from "../contexts/props";
 import { debounce } from "../helpers/debounce";
 import { getClassNamesForModifiers } from "../helpers/getClassNamesForModifiers";
 import { getStyleForModifiers } from "../helpers/getStyleForModifiers";
+import { useMultiContext } from "../selection/multi";
+import { useRangeContext } from "../selection/range";
+import { useSingleContext } from "../selection/single";
 
 import { DayProps, Day as DefaultDay } from "./Day";
 import { DayDateProps, DayDate as DefaultDayDate } from "./DayDate";
@@ -34,7 +27,7 @@ export function DayWrapper(props: {
   /** The day to be rendered in the gridcell. */
   day: CalendarDay;
 }) {
-  const cellRef = useRef<HTMLDivElement>(null);
+  const cellRef = React.useRef<HTMLDivElement>(null);
 
   const {
     classNames,
@@ -43,6 +36,7 @@ export function DayWrapper(props: {
     formatters: { formatDay },
     labels: { labelDay },
     locale,
+    mode,
     modifiersClassNames = {},
     modifiersStyles = {},
     onDayFocus,
@@ -60,15 +54,18 @@ export function DayWrapper(props: {
     onDayTouchMove,
     onDayTouchStart,
     styles = {}
-  } = useProps();
+  } = usePropsContext();
 
-  const { isInteractive } = useCalendar();
-  const { setSelected } = useSelection();
-  const { getModifiers } = useModifiers();
+  const { isInteractive } = useCalendarContext();
+  const { getModifiers } = useModifiersContext();
+
+  const single = useSingleContext();
+  const multi = useMultiContext();
+  const range = useRangeContext();
 
   const {
     autoFocusTarget,
-    focusedDay,
+    focused,
     focus,
     blur,
     focusDayBefore,
@@ -81,23 +78,37 @@ export function DayWrapper(props: {
     focusYearAfter,
     focusStartOfWeek,
     focusEndOfWeek
-  } = useFocus();
+  } = useFocusContext();
+
   const modifiers = getModifiers(props.day);
 
-  const onClick: MouseEventHandler = (e) => {
+  const onClick: React.MouseEventHandler = (e) => {
     if (modifiers.disabled) {
       e.preventDefault();
       e.stopPropagation();
       return;
     }
-    setSelected(props.day.date, modifiers, e);
+
+    switch (mode) {
+      case "single":
+        single.setSelected(props.day.date, modifiers, e);
+        break;
+      case "multiple":
+        multi.setSelected(props.day.date, modifiers, e);
+        break;
+      case "range":
+        range.setSelected(props.day.date, modifiers, e);
+        break;
+    }
+
     if (modifiers.focusable) {
       focus(props.day);
     }
+
     onDayClick?.(props.day.date, modifiers, e);
   };
 
-  const onFocus: FocusEventHandler = (e) => {
+  const onFocus: React.FocusEventHandler = (e) => {
     if (modifiers.disabled) {
       e.preventDefault();
       e.stopPropagation();
@@ -107,45 +118,45 @@ export function DayWrapper(props: {
     onDayFocus?.(props.day.date, modifiers, e);
   };
 
-  const onBlur: FocusEventHandler = (e) => {
+  const onBlur: React.FocusEventHandler = (e) => {
     blur();
     onDayBlur?.(props.day.date, modifiers, e);
   };
 
-  const onMouseEnter: MouseEventHandler = (e) => {
+  const onMouseEnter: React.MouseEventHandler = (e) => {
     onDayMouseEnter?.(props.day.date, modifiers, e);
   };
-  const onMouseLeave: MouseEventHandler = (e) => {
+  const onMouseLeave: React.MouseEventHandler = (e) => {
     onDayMouseLeave?.(props.day.date, modifiers, e);
   };
-  const onPointerEnter: PointerEventHandler = (e) => {
+  const onPointerEnter: React.PointerEventHandler = (e) => {
     onDayPointerEnter?.(props.day.date, modifiers, e);
   };
-  const onPointerLeave: PointerEventHandler = (e) => {
+  const onPointerLeave: React.PointerEventHandler = (e) => {
     onDayPointerLeave?.(props.day.date, modifiers, e);
   };
-  const onTouchCancel: TouchEventHandler = (e) => {
+  const onTouchCancel: React.TouchEventHandler = (e) => {
     onDayTouchCancel?.(props.day.date, modifiers, e);
   };
-  const onTouchEnd: TouchEventHandler = (e) => {
+  const onTouchEnd: React.TouchEventHandler = (e) => {
     onDayTouchEnd?.(props.day.date, modifiers, e);
   };
-  const onTouchMove: TouchEventHandler = (e) => {
+  const onTouchMove: React.TouchEventHandler = (e) => {
     onDayTouchMove?.(props.day.date, modifiers, e);
   };
-  const onTouchStart: TouchEventHandler = (e) => {
+  const onTouchStart: React.TouchEventHandler = (e) => {
     onDayTouchStart?.(props.day.date, modifiers, e);
   };
 
-  const onKeyUp: KeyboardEventHandler = (e) => {
+  const onKeyUp: React.KeyboardEventHandler = (e) => {
     onDayKeyUp?.(props.day.date, modifiers, e);
   };
 
-  const onKeyPress: KeyboardEventHandler = (e) => {
+  const onKeyPress: React.KeyboardEventHandler = (e) => {
     onDayKeyPress?.(props.day.date, modifiers, e);
   };
 
-  const onKeyDown: KeyboardEventHandler = (e) => {
+  const onKeyDown: React.KeyboardEventHandler = (e) => {
     switch (e.key) {
       case "ArrowLeft":
         e.preventDefault();
@@ -171,7 +182,15 @@ export function DayWrapper(props: {
       case "Enter":
         e.preventDefault();
         e.stopPropagation();
-        setSelected(props.day.date, modifiers, e);
+        if (mode === "single" && !modifiers.disabled) {
+          single.setSelected(props.day.date, modifiers, e);
+        }
+        if (mode === "multiple" && !modifiers.disabled) {
+          multi.setSelected(props.day.date, modifiers, e);
+        }
+        if (mode === "range" && !modifiers.disabled) {
+          range.setSelected(props.day.date, modifiers, e);
+        }
         break;
       case "PageUp":
         e.preventDefault();
@@ -198,7 +217,7 @@ export function DayWrapper(props: {
   };
 
   const isAutoFocusTarget = Boolean(autoFocusTarget?.isEqualTo(props.day));
-  const isFocused = Boolean(focusedDay?.isEqualTo(props.day));
+  const isFocused = Boolean(focused?.isEqualTo(props.day));
 
   const style = getStyleForModifiers(modifiers, modifiersStyles, styles);
 
@@ -211,7 +230,7 @@ export function DayWrapper(props: {
   const className = [classNames[UI.Day], ...classNameForModifiers];
 
   if (isFocused) {
-    className.push(classNames[DayModifier.focused]);
+    className.push(classNames[DayFlag.focused]);
   }
 
   const dayRootProps: DayProps["rootProps"] = {
@@ -242,14 +261,14 @@ export function DayWrapper(props: {
     ref: cellRef
   };
 
-  useEffect(() => {
+  React.useEffect(() => {
     if (!cellRef.current) return; // no element to focus
-    if (!focusedDay) return; // no day to focus
-    if (!props.day.isEqualTo(focusedDay)) return; // not this day`
+    if (!focused) return; // no day to focus
+    if (!props.day.isEqualTo(focused)) return; // not this day`
     if (modifiers.disabled || modifiers.hidden) return; // cannot focus
 
     cellRef.current.focus();
-  }, [focusedDay, modifiers.disabled, modifiers.hidden, props.day]);
+  }, [focused, modifiers.disabled, modifiers.hidden, props.day]);
 
   const Day = components?.Day ?? DefaultDay;
 
diff --git a/src/components/Dropdown.tsx b/src/components/Dropdown.tsx
index 52aaa2d4d..71688b933 100644
--- a/src/components/Dropdown.tsx
+++ b/src/components/Dropdown.tsx
@@ -1,9 +1,9 @@
-import React, { SelectHTMLAttributes } from "react";
+import React, { type SelectHTMLAttributes } from "react";
 
 import type { Month } from "date-fns";
 
 import { UI } from "../UI";
-import { useProps } from "../contexts/props";
+import { usePropsContext } from "../contexts/props";
 
 import { Chevron as DefaultChevron } from "./Chevron";
 import { Option as DefaultOption } from "./Option";
@@ -37,7 +37,7 @@ export function Dropdown(
   } & Omit<SelectHTMLAttributes<HTMLSelectElement>, "children">
 ) {
   const { options, rootClassName, className, ...selectProps } = props;
-  const { classNames, components } = useProps();
+  const { classNames, components } = usePropsContext();
 
   const cssClassRoot = [classNames[UI.DropdownRoot], rootClassName].join(" ");
   const cssClassSelect = [classNames[UI.Dropdown], className].join(" ");
diff --git a/src/components/DropdownNav.tsx b/src/components/DropdownNav.tsx
index f998fd2c5..7731fdb73 100644
--- a/src/components/DropdownNav.tsx
+++ b/src/components/DropdownNav.tsx
@@ -2,7 +2,7 @@ import React from "react";
 
 import { UI } from "../UI";
 import type { CalendarMonth } from "../classes";
-import { useProps } from "../contexts/props";
+import { usePropsContext } from "../contexts/props";
 
 import { MonthsDropdown } from "./MonthsDropdown";
 import { YearsDropdown } from "./YearsDropdown";
@@ -25,7 +25,7 @@ export function DropdownNav(props: {
   /** The index where this month is displayed. */
   index: number;
 }) {
-  const { classNames, styles } = useProps();
+  const { classNames, styles } = usePropsContext();
 
   return (
     <div
diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx
index 6632a32a5..20893bcda 100644
--- a/src/components/Footer.tsx
+++ b/src/components/Footer.tsx
@@ -1,4 +1,4 @@
-import React, { HTMLProps } from "react";
+import React, { type HTMLProps } from "react";
 
 /**
  * Component wrapping the footer.
diff --git a/src/components/Month.tsx b/src/components/Month.tsx
index e5bb13d33..ef8d1a8a1 100644
--- a/src/components/Month.tsx
+++ b/src/components/Month.tsx
@@ -1,8 +1,8 @@
-import React, { useId } from "react";
+import React from "react";
 
 import { UI } from "../UI";
 import type { CalendarMonth } from "../classes/CalendarMonth";
-import { useProps } from "../contexts/props";
+import { usePropsContext } from "../contexts/props";
 
 import { MonthCaption as DefaultMonthCaption } from "./MonthCaption";
 import { Week as DefaultWeek } from "./Week";
@@ -24,9 +24,9 @@ export function Month(props: {
   index: number;
 }) {
   const { id, mode, hideWeekdayRow, components, classNames, styles } =
-    useProps();
+    usePropsContext();
 
-  const reactId = useId();
+  const reactId = React.useId();
   const captionId = id ? `${id}-caption-${props.index}` : reactId;
   const gridId = id ? `${id}-grid-${props.index}` : reactId;
 
diff --git a/src/components/MonthCaption.tsx b/src/components/MonthCaption.tsx
index 5804b60e7..9feaf01a9 100644
--- a/src/components/MonthCaption.tsx
+++ b/src/components/MonthCaption.tsx
@@ -2,7 +2,7 @@ import React from "react";
 
 import { UI } from "../UI";
 import type { CalendarMonth } from "../classes";
-import { useProps } from "../contexts/props";
+import { usePropsContext } from "../contexts/props";
 
 import { DropdownNav } from "./DropdownNav";
 
@@ -29,7 +29,7 @@ export function MonthCaption(props: {
     labels: { labelCaption },
     locale,
     styles
-  } = useProps();
+  } = usePropsContext();
 
   return (
     <div
diff --git a/src/components/MonthsDropdown.tsx b/src/components/MonthsDropdown.tsx
index b5f96600c..2f3d779b5 100644
--- a/src/components/MonthsDropdown.tsx
+++ b/src/components/MonthsDropdown.tsx
@@ -6,8 +6,8 @@ import { startOfMonth } from "date-fns/startOfMonth";
 
 import { UI } from "../UI";
 import type { CalendarMonth } from "../classes";
-import { useCalendar } from "../contexts/calendar";
-import { useProps } from "../contexts/props";
+import { useCalendarContext } from "../contexts/calendar";
+import { usePropsContext } from "../contexts/props";
 
 import { Dropdown as DefaultDropdown } from "./Dropdown";
 
@@ -28,9 +28,9 @@ export function MonthsDropdown(props: {
     components,
     disableNavigation,
     labels: { labelMonthDropdown }
-  } = useProps();
+  } = usePropsContext();
 
-  const { dropdownOptions: dropdown, goToMonth } = useCalendar();
+  const { dropdownOptions, goToMonth } = useCalendarContext();
 
   const Dropdown = components?.Dropdown ?? DefaultDropdown;
 
@@ -45,7 +45,7 @@ export function MonthsDropdown(props: {
       aria-label={labelMonthDropdown()}
       disabled={Boolean(disableNavigation)}
       rootClassName={classNames[UI.MonthsDropdown]}
-      options={dropdown.months}
+      options={dropdownOptions.months}
       value={props.month.date.getMonth()}
       onChange={handleChange}
     />
diff --git a/src/components/Nav.tsx b/src/components/Nav.tsx
index 98f9d869c..ae084c4bd 100644
--- a/src/components/Nav.tsx
+++ b/src/components/Nav.tsx
@@ -1,8 +1,8 @@
 import React, { MouseEventHandler } from "react";
 
 import { UI } from "../UI";
-import { useCalendar } from "../contexts/calendar";
-import { useProps } from "../contexts/props";
+import { useCalendarContext } from "../contexts/calendar";
+import { usePropsContext } from "../contexts/props";
 
 import { Button as DefaultButton } from "./Button";
 import { Chevron as DefaultChevron } from "./Chevron";
@@ -25,9 +25,9 @@ export function Nav() {
     id,
     onNextClick,
     onPrevClick
-  } = useProps();
+  } = usePropsContext();
 
-  const calendar = useCalendar();
+  const calendar = useCalendarContext();
 
   const handlePreviousClick = () => {
     if (!calendar.previousMonth) return;
diff --git a/src/components/Week.tsx b/src/components/Week.tsx
index 682244eb1..cac07181e 100644
--- a/src/components/Week.tsx
+++ b/src/components/Week.tsx
@@ -4,7 +4,7 @@ import { getUnixTime } from "date-fns/getUnixTime";
 
 import { UI } from "../UI";
 import type { CalendarDay, CalendarWeek } from "../classes";
-import { useProps } from "../contexts/props";
+import { usePropsContext } from "../contexts/props";
 
 import { DayWrapper } from "./DayWrapper";
 import { WeekNumber as DefaultWeekNumber } from "./WeekNumber";
@@ -18,7 +18,7 @@ import { WeekNumber as DefaultWeekNumber } from "./WeekNumber";
  * @see https://react-day-picker.js.org/advanced-guides/custom-components
  */
 export function Week(props: { ["aria-rowindex"]: number; week: CalendarWeek }) {
-  const { styles, classNames, showWeekNumber, components } = useProps();
+  const { styles, classNames, showWeekNumber, components } = usePropsContext();
 
   const WeekNumber = components?.WeekNumber ?? DefaultWeekNumber;
 
diff --git a/src/components/WeekNumber.tsx b/src/components/WeekNumber.tsx
index 1087ba888..4f75b0d43 100644
--- a/src/components/WeekNumber.tsx
+++ b/src/components/WeekNumber.tsx
@@ -2,7 +2,7 @@ import React from "react";
 
 import { UI, WeekNumberFlag } from "../UI";
 import type { CalendarWeek } from "../classes";
-import { useProps } from "../contexts/props";
+import { usePropsContext } from "../contexts/props";
 
 /**
  * Render the cell with the number of the week.
@@ -20,7 +20,7 @@ export function WeekNumber(props: { week: CalendarWeek }) {
     locale,
     styles,
     onWeekNumberClick
-  } = useProps();
+  } = usePropsContext();
 
   const isInteractive = Boolean(onWeekNumberClick);
 
@@ -31,7 +31,7 @@ export function WeekNumber(props: { week: CalendarWeek }) {
       aria-label={labelWeekNumber(props.week.weekNumber, { locale })}
       className={[
         classNames[UI.WeekNumber],
-        isInteractive ? classNames[WeekNumberFlag.isInteractive] : ""
+        isInteractive ? classNames[WeekNumberFlag.week_number_interactive] : ""
       ].join(" ")}
       style={styles?.[UI.WeekNumber]}
       tabIndex={isInteractive ? 0 : undefined}
diff --git a/src/components/Weekday.tsx b/src/components/Weekday.tsx
index 710aa2109..7121d6b6e 100644
--- a/src/components/Weekday.tsx
+++ b/src/components/Weekday.tsx
@@ -1,7 +1,7 @@
 import React from "react";
 
 import { UI } from "../UI";
-import { useProps } from "../contexts/props";
+import { usePropsContext } from "../contexts/props";
 
 /**
  * Render the column header with the weekday name (e.g. "Mo", "Tu", etc.).
@@ -23,7 +23,7 @@ export function Weekday(props: {
     locale,
     hideWeekdayRow,
     styles
-  } = useProps();
+  } = usePropsContext();
   return (
     <span
       role="columnheader"
diff --git a/src/components/Weekdays.tsx b/src/components/Weekdays.tsx
index 5a04cd301..51bcc0350 100644
--- a/src/components/Weekdays.tsx
+++ b/src/components/Weekdays.tsx
@@ -1,7 +1,7 @@
 import React from "react";
 
 import { UI } from "../UI";
-import { useProps } from "../contexts/props";
+import { usePropsContext } from "../contexts/props";
 import { getWeekdays } from "../helpers/getWeekdays";
 
 import { Weekday as DefaultWeekday } from "./Weekday";
@@ -24,7 +24,7 @@ export function Weekdays() {
     showWeekNumber,
     styles,
     weekStartsOn
-  } = useProps();
+  } = usePropsContext();
 
   const weekdays = getWeekdays(locale, weekStartsOn, ISOWeek);
   const Weekday = components?.Weekday ?? DefaultWeekday;
diff --git a/src/components/YearsDropdown.tsx b/src/components/YearsDropdown.tsx
index db66b2fe7..07c535c2b 100644
--- a/src/components/YearsDropdown.tsx
+++ b/src/components/YearsDropdown.tsx
@@ -6,8 +6,8 @@ import { startOfMonth } from "date-fns/startOfMonth";
 
 import { UI } from "../UI";
 import type { CalendarMonth } from "../classes/CalendarMonth";
-import { useCalendar } from "../contexts/calendar";
-import { useProps } from "../contexts/props";
+import { useCalendarContext } from "../contexts/calendar";
+import { usePropsContext } from "../contexts/props";
 
 import { Dropdown as DefaultDropdown } from "./Dropdown";
 
@@ -28,9 +28,9 @@ export function YearsDropdown(props: {
     components,
     disableNavigation,
     labels: { labelYearDropdown }
-  } = useProps();
+  } = usePropsContext();
 
-  const { dropdownOptions: dropdown, goToMonth } = useCalendar();
+  const { dropdownOptions, goToMonth } = useCalendarContext();
 
   const handleChange: ChangeEventHandler<HTMLSelectElement> = (e) => {
     const month = setYear(
@@ -46,7 +46,7 @@ export function YearsDropdown(props: {
       aria-label={labelYearDropdown()}
       disabled={Boolean(disableNavigation)}
       rootClassName={classNames[UI.YearsDropdown]}
-      options={dropdown.years}
+      options={dropdownOptions.years}
       value={props.month.date.getFullYear()}
       onChange={handleChange}
     />
diff --git a/src/contexts/calendar.tsx b/src/contexts/calendar.tsx
index e668a0a8a..aeaf6142b 100644
--- a/src/contexts/calendar.tsx
+++ b/src/contexts/calendar.tsx
@@ -1,32 +1,35 @@
-import React from "react";
-import { createContext, type ReactNode, useContext } from "react";
+import React, { type ReactElement, createContext, useContext } from "react";
 
 import { startOfMonth } from "date-fns/startOfMonth";
 
-import type { CalendarWeek, CalendarMonth, CalendarDay } from "../classes";
-import { DropdownOption } from "../components/Dropdown";
+import type { CalendarWeek, CalendarDay, CalendarMonth } from "../classes";
+import type { DropdownOption } from "../components/Dropdown";
 import { getDates } from "../helpers/getDates";
 import { getDays } from "../helpers/getDays";
 import { getDisplayMonths } from "../helpers/getDisplayMonths";
 import { getDropdownMonths } from "../helpers/getDropdownMonths";
 import { getDropdownYears } from "../helpers/getDropdownYears";
+import { getInitialMonth } from "../helpers/getInitialMonth";
 import { getMonths } from "../helpers/getMonths";
 import { getNextMonth } from "../helpers/getNextMonth";
 import { getPreviousMonth } from "../helpers/getPreviousMonth";
-import { getStartMonth } from "../helpers/getStartMonth";
 import { getWeeks } from "../helpers/getWeeks";
 import { useControlledValue } from "../helpers/useControlledValue";
 
-import { useProps } from "./props";
+import { usePropsContext } from "./props";
+
+/** @private */
+export const CalendarContext = createContext<CalendarContextValue | undefined>(
+  undefined
+);
 
 /**
- * Share the calendar state and navigation methods across the components.
- *
- * Access the calendar context using the {@link useCalendar} hook.
+ * Share the calendar state and navigation methods across the components.\
  *
- * @group Contexts
+ * Access the calendar context using the {@link useCalendarContext} hook.
  */
-export interface CalendarContext {
+export type CalendarContextValue = {
+  today: Date;
   /** All the unique dates displayed to the calendar. */
   dates: Date[];
   /**
@@ -40,16 +43,11 @@ export interface CalendarContext {
   /** The months displayed in the calendar. */
   months: CalendarMonth[];
   /**
-   * The month displayed as first the calendar. When
-   * {@link PropsBase.numberOfMonths} is greater than `1`, it is the first of the
-   * displayed months.
+   * The month displayed as first the calendar. When `numberOfMonths` is greater
+   * than `1`, it is the first of the displayed months.
    */
   firstMonth: Date;
-  /**
-   * The month displayed as last the calendar. When
-   * {@link PropsBase.numberOfMonths} is greater than `1`, it is the last of the
-   * displayed months.
-   */
+  /** The month displayed as last the calendar. */
   lastMonth: Date;
   /** The next month to display. */
   nextMonth: Date | undefined;
@@ -62,10 +60,16 @@ export interface CalendarContext {
     /** The options to use in the years dropdown. */
     years: DropdownOption[] | undefined;
   };
+
+  /** Set the first month displayed in the calendar. */
+  setFirstMonth: (date: Date) => void;
+
   /**
-   * Navigate to the specified month. Will fire the
-   * {@link PropsBase.onMonthChange} callback.
+   * Whether the calendar is interactive, i.e. DayPicker has a selection `mode`
+   * set or the `onDayClick` prop is set.
    */
+  isInteractive: boolean;
+  /** Navigate to the specified month. Will fire the `onMonthChange` callback. */
   goToMonth: (month: Date) => void;
   /** Navigate to the next month. */
   goToNextMonth: () => void;
@@ -83,30 +87,21 @@ export interface CalendarContext {
   goToDay: (day: CalendarDay) => void;
   /** Whether the given date is included in the displayed months. */
   isDayDisplayed: (day: CalendarDay) => boolean;
+};
 
-  /**
-   * Whether the calendar is interactive, i.e. DayPicker has a selection `mode`
-   * set or the `onDayClick` prop is set.
-   */
-  isInteractive: boolean;
-}
-
-const calendarContext = createContext<CalendarContext | undefined>(undefined);
+function useCalendar(): CalendarContextValue {
+  const props = usePropsContext();
 
-/** @private */
-export function CalendarProvider(providerProps: { children?: ReactNode }) {
-  const props = useProps();
-
-  const startMonth = getStartMonth(props);
+  const initialDisplayMonth = getInitialMonth(props);
 
   // The first month displayed in the calendar
-  const [firstMonth, setFirstMonth] = useControlledValue(
-    startMonth,
+  const [firstDisplayedMonth, setFirstMonth] = useControlledValue(
+    initialDisplayMonth,
     props.month ? startOfMonth(props.month) : undefined
   );
 
   /** An array of the months displayed in the calendar. */
-  const displayMonths = getDisplayMonths(firstMonth, props);
+  const displayMonths = getDisplayMonths(firstDisplayedMonth, props);
 
   /** The last month displayed in the calendar. */
   const lastMonth = displayMonths[displayMonths.length - 1];
@@ -123,8 +118,13 @@ export function CalendarProvider(providerProps: { children?: ReactNode }) {
   /** An array of the Days displayed in the calendar. */
   const days = getDays(months);
 
-  const nextMonth = getNextMonth(firstMonth, props);
-  const previousMonth = getPreviousMonth(firstMonth, props);
+  const previousMonth = getPreviousMonth(firstDisplayedMonth, props);
+  const nextMonth = getNextMonth(firstDisplayedMonth, props);
+
+  const isInteractive =
+    props.mode !== undefined || props.onDayClick !== undefined;
+
+  const { disableNavigation, onMonthChange, startMonth, endMonth } = props;
 
   function isDayDisplayed(day: CalendarDay) {
     return weeks.some((week: CalendarWeek) => {
@@ -135,20 +135,20 @@ export function CalendarProvider(providerProps: { children?: ReactNode }) {
   }
 
   function goToMonth(date: Date) {
-    if (props.disableNavigation) {
+    if (disableNavigation) {
       return;
     }
     let newMonth = startOfMonth(date);
     // if month is before startMonth, use the first month instead
-    if (props.startMonth && newMonth < startOfMonth(props.startMonth)) {
-      newMonth = startOfMonth(props.startMonth);
+    if (startMonth && newMonth < startOfMonth(startMonth)) {
+      newMonth = startOfMonth(startMonth);
     }
     // if month is after endMonth, use the last month instead
-    if (props.endMonth && newMonth > startOfMonth(props.endMonth)) {
-      newMonth = startOfMonth(props.endMonth);
+    if (endMonth && newMonth > startOfMonth(endMonth)) {
+      newMonth = startOfMonth(endMonth);
     }
     setFirstMonth(newMonth);
-    props.onMonthChange?.(newMonth);
+    onMonthChange?.(newMonth);
   }
 
   function goToDay(day: CalendarDay) {
@@ -175,53 +175,61 @@ export function CalendarProvider(providerProps: { children?: ReactNode }) {
     return previousMonth ? goToMonth(previousMonth) : undefined;
   }
 
-  const isInteractive =
-    props.mode !== "default" || props.onDayClick !== undefined;
-
-  const calendar: CalendarContext = {
+  const calendarContextValue: CalendarContextValue = {
     dates,
     months,
     weeks,
     days,
 
-    firstMonth,
+    today: props.today,
+
+    firstMonth: firstDisplayedMonth,
     lastMonth,
     previousMonth,
     nextMonth,
 
-    goToMonth,
-    goToNextMonth,
-    goToPreviousMonth,
-    goToDay,
+    setFirstMonth,
 
     isInteractive,
-    isDayDisplayed,
 
     dropdownOptions: {
-      months: getDropdownMonths(props, firstMonth.getFullYear()),
-      years: getDropdownYears(props, lastMonth.getMonth())
-    }
+      months: getDropdownMonths(firstDisplayedMonth, props),
+      years: getDropdownYears(firstDisplayedMonth, props)
+    },
+    isDayDisplayed,
+    goToMonth,
+    goToDay,
+    goToNextMonth,
+    goToPreviousMonth
   };
 
+  return calendarContextValue;
+}
+
+/** @private */
+export function CalendarContextProvider(props: { children: ReactElement }) {
+  const calendarContextValue = useCalendar();
   return (
-    <calendarContext.Provider value={calendar}>
-      {providerProps.children}
-    </calendarContext.Provider>
+    <CalendarContext.Provider value={calendarContextValue}>
+      {props.children}
+    </CalendarContext.Provider>
   );
 }
 
 /**
- * Return the calendar state and navigation methods to navigate the calendar.
+ * Access to the props passed to `DayPicker`, with some meaningful defaults.
  *
  * Use this hook from the custom components passed via the `components` prop.
  *
- * @group Hooks
+ * @group Contexts
  * @see https://react-day-picker.js.org/advanced-guides/custom-components
  */
-export function useCalendar(): CalendarContext {
-  const context = useContext(calendarContext);
-  if (!context)
-    throw new Error(`useCalendar must be used within a CalendarProvider.`);
-
-  return context;
+export function useCalendarContext() {
+  const calendarContext = useContext(CalendarContext);
+  if (!calendarContext) {
+    throw new Error(
+      "useCalendarContext() must be used within a CalendarContextProvider"
+    );
+  }
+  return calendarContext;
 }
diff --git a/src/contexts/focus.tsx b/src/contexts/focus.tsx
index f81a8d478..75812dd0b 100644
--- a/src/contexts/focus.tsx
+++ b/src/contexts/focus.tsx
@@ -1,39 +1,25 @@
 import React, {
-  createContext,
   ReactNode,
+  createContext,
   useContext,
   useEffect,
   useState
 } from "react";
 
-import { DayModifier } from "../UI";
+import { DayFlag } from "../UI";
 import type { CalendarDay } from "../classes";
 import { getNextFocus } from "../helpers/getNextFocus";
+import type { MoveFocusBy, MoveFocusDir, Mode } from "../types";
 
-import { useCalendar } from "./calendar";
-import { useModifiers } from "./modifiers";
-import { useProps } from "./props";
-
-export type MoveFocusBy =
-  | "day"
-  | "week"
-  | "startOfWeek"
-  | "endOfWeek"
-  | "month"
-  | "year";
+import { useCalendarContext } from "./calendar";
+import { useModifiersContext } from "./modifiers";
+import { usePropsContext } from "./props";
 
-export type MoveFocusDir = "after" | "before";
+const FocusContext = createContext<FocusContextValue | undefined>(undefined);
 
-/**
- * Share the focused day and the methods to move the focus.
- *
- * Access this context from the {@link useFocus} hook.
- *
- * @group Contexts
- */
-export interface FocusContext {
+export type FocusContextValue = {
   /** The date that is currently focused. */
-  focusedDay: CalendarDay | undefined;
+  focused: CalendarDay | undefined;
   /**
    * The date that is target of the focus when tabbing into the month grid. The
    * focus target is the selected date first, then the today date, then the
@@ -41,8 +27,11 @@ export interface FocusContext {
    */
   autoFocusTarget: CalendarDay | undefined;
   initiallyFocused: boolean;
-  /** Focus a date. */
+  /** Focus the given day. */
   focus: (day: CalendarDay | undefined) => void;
+  /** Set the last focused day. */
+  setLastFocused: (day: CalendarDay | undefined) => void;
+
   /** Blur the focused day. */
   blur: () => void;
   /** Focus the day after the focused day. */
@@ -65,31 +54,38 @@ export interface FocusContext {
   focusStartOfWeek: () => void;
   /* Focus the day at the end of the week of focused day. */
   focusEndOfWeek: () => void;
-}
+};
 
-const focusContext = createContext<FocusContext | undefined>(undefined);
+/**
+ * Share the focused day and the methods to move the focus.
+ *
+ * Use this hook from the custom components passed via the `components` prop.
+ *
+ * @group Contexts
+ * @see https://react-day-picker.js.org/advanced-guides/custom-components
+ */
 
-/** @private */
-export function FocusProvider({
-  children
-}: {
-  children: ReactNode;
-}): JSX.Element {
-  const { goToDay, isDayDisplayed, days, isInteractive } = useCalendar();
+function useFocus(): FocusContextValue {
+  const { goToDay, isDayDisplayed, isInteractive } = useCalendarContext();
 
-  const { autoFocus = false, ...props } = useProps();
-  const { calendarModifiers, getModifiers } = useModifiers();
+  const props = usePropsContext();
+  const { autoFocus } = props;
+  const {
+    dayFlags: internal,
+    selectionStates: selection,
+    getModifiers
+  } = useModifiersContext();
 
-  const [focused, setFocused] = useState<CalendarDay | undefined>();
+  const [focused, focus] = useState<CalendarDay | undefined>();
   const [lastFocused, setLastFocused] = useState<CalendarDay | undefined>();
   const [initiallyFocused, setInitiallyFocused] = useState(false);
 
-  const today = calendarModifiers.today[0];
+  const today = internal.today[0];
 
   let autoFocusTarget: CalendarDay | undefined;
 
   const isValidFocusTarget = (day: CalendarDay) => {
-    return isDayDisplayed(day) && !getModifiers(day)[DayModifier.disabled];
+    return isDayDisplayed(day) && !getModifiers(day)[DayFlag.disabled];
   };
 
   if (isInteractive) {
@@ -98,14 +94,14 @@ export function FocusProvider({
     } else if (lastFocused) {
       autoFocusTarget = lastFocused;
     } else if (
-      calendarModifiers.selected[0] &&
-      isValidFocusTarget(calendarModifiers.selected[0])
+      selection.selected[0] &&
+      isValidFocusTarget(selection.selected[0])
     ) {
-      autoFocusTarget = calendarModifiers.selected[0];
+      autoFocusTarget = selection.selected[0];
     } else if (today && isValidFocusTarget(today)) {
       autoFocusTarget = today;
-    } else if (calendarModifiers.focusable[0]) {
-      autoFocusTarget = calendarModifiers.focusable[0];
+    } else if (internal.focusable[0]) {
+      autoFocusTarget = internal.focusable[0];
     }
   }
 
@@ -114,14 +110,15 @@ export function FocusProvider({
     if (!autoFocus) return;
     if (!autoFocusTarget) return;
     if (!initiallyFocused) return;
+    // TODO: bug here?
     setInitiallyFocused(true);
-    setFocused(autoFocusTarget);
+    focus(autoFocusTarget);
   }, [autoFocus, autoFocusTarget, focused, initiallyFocused]);
 
-  function blur() {
+  const blur = () => {
     setLastFocused(focused);
-    setFocused(undefined);
-  }
+    focus(undefined);
+  };
 
   function moveFocus(moveBy: MoveFocusBy, moveDir: MoveFocusDir) {
     if (!focused) return;
@@ -129,15 +126,16 @@ export function FocusProvider({
     if (!nextFocus) return;
 
     goToDay(nextFocus);
-    setFocused(nextFocus);
+    focus(nextFocus);
   }
 
-  const value: FocusContext = {
+  const contextValue: FocusContextValue = {
     autoFocusTarget,
     initiallyFocused,
-    focusedDay: focused,
+    focus,
+    focused,
+    setLastFocused,
     blur,
-    focus: setFocused,
     focusDayAfter: () => moveFocus("day", "after"),
     focusDayBefore: () => moveFocus("day", "before"),
     focusWeekAfter: () => moveFocus("week", "after"),
@@ -150,23 +148,30 @@ export function FocusProvider({
     focusEndOfWeek: () => moveFocus("endOfWeek", "after")
   };
 
+  return contextValue;
+}
+
+/** @private */
+export function FocusContextProvider<
+  ModeType extends Mode | undefined = undefined,
+  IsRequired extends boolean = false
+>({ children }: { children: ReactNode }) {
+  const focusContextValue = useFocus();
+
   return (
-    <focusContext.Provider value={value}>{children}</focusContext.Provider>
+    <FocusContext.Provider value={focusContextValue}>
+      {children}
+    </FocusContext.Provider>
   );
 }
 
-/**
- * Share the focused day and the methods to move the focus.
- *
- * Use this hook from the custom components passed via the `components` prop.
- *
- * @group Hooks
- * @see https://react-day-picker.js.org/advanced-guides/custom-components
- */
-export function useFocus(): FocusContext {
-  const context = useContext(focusContext);
-  if (!context) {
-    throw new Error("useFocus must be used within a FocusProvider");
+/** @group Contexts */
+export function useFocusContext() {
+  const propsContext = useContext(FocusContext);
+  if (!propsContext) {
+    throw new Error(
+      "useFocusContext() must be used within a FocusContextProvider"
+    );
   }
-  return context;
+  return propsContext;
 }
diff --git a/src/contexts/modifiers.tsx b/src/contexts/modifiers.tsx
index 4c9b8ae79..9451f6b51 100644
--- a/src/contexts/modifiers.tsx
+++ b/src/contexts/modifiers.tsx
@@ -1,40 +1,43 @@
-import React, { createContext, useContext } from "react";
-import type { ReactNode } from "react";
+import React, { type ReactElement, createContext, useContext } from "react";
 
 import { isSameDay } from "date-fns/isSameDay";
 import { isSameMonth } from "date-fns/isSameMonth";
 
-import type { CalendarDay } from "../classes";
+import { DayFlag, SelectionState } from "../UI";
+import { CalendarDay } from "../classes";
+import { useMultiContext } from "../selection/multi";
+import { useRangeContext } from "../selection/range";
+import { useSingleContext } from "../selection/single";
 import type {
-  DayModifiers,
-  InternalModifier,
-  CalendarModifiers
+  CustomModifiers,
+  DayFlags,
+  Modifiers,
+  SelectionStates
 } from "../types";
+import { isDateInRange } from "../utils";
 import { dateMatchModifiers } from "../utils/dateMatchModifiers";
 
-import { useCalendar } from "./calendar";
-import { useProps } from "./props";
-import { useSelection } from "./selection";
-
-/**
- * Holds all the modifiers used in the the calendar.
- *
- * Use the Modifiers context in custom component by calling the
- * {@link useModifiers} hook.
- *
- * @group Contexts
- */
-export interface ModifiersContext {
-  /** Return the modifiers of the specified day. */
-  getModifiers: (day: CalendarDay) => DayModifiers;
-  /** A map of all the modifiers used by the calendar. */
-  calendarModifiers: CalendarModifiers;
-}
-
-const modifiersContext = createContext<ModifiersContext | undefined>(undefined);
+import { useCalendarContext } from "./calendar";
+import { usePropsContext } from "./props";
 
 /** @private */
-export function ModifiersProvider({ children }: { children: ReactNode }) {
+export const ModifiersContext = createContext<
+  ModifiersContextValue | undefined
+>(undefined);
+
+/** Maps of all the modifiers with the calendar days. */
+export type ModifiersContextValue = {
+  /** List the days with custom modifiers passed via the `modifiers` prop. */
+  customModifiers: Record<string, CalendarDay[]>;
+  /** List the days with the internal modifiers. */
+  dayFlags: Record<DayFlag, CalendarDay[]>;
+  /** List the days with selection modifiers. */
+  selectionStates: Record<SelectionState, CalendarDay[]>;
+  /** Get the modifiers for a given day. */
+  getModifiers: (day: CalendarDay) => Modifiers;
+};
+
+function useModifiers(): ModifiersContextValue {
   const {
     disabled,
     hidden,
@@ -43,27 +46,31 @@ export function ModifiersProvider({ children }: { children: ReactNode }) {
     onDayClick,
     showOutsideDays,
     today
-  } = useProps();
-  const calendar = useCalendar();
-  const selection = useSelection();
-
-  /** Modifiers that are set internally. */
-  const internal: Record<InternalModifier, CalendarDay[]> = {
-    focused: [],
-    outside: [],
-    disabled: [],
-    hidden: [],
-    today: [],
-    focusable: [],
-    selected: [],
-    range_start: [],
-    range_middle: [],
-    range_end: []
+  } = usePropsContext();
+
+  const calendar = useCalendarContext();
+  const single = useSingleContext();
+  const multi = useMultiContext();
+  const range = useRangeContext();
+
+  const internal: Record<DayFlag, CalendarDay[]> = {
+    [DayFlag.focused]: [],
+    [DayFlag.outside]: [],
+    [DayFlag.disabled]: [],
+    [DayFlag.hidden]: [],
+    [DayFlag.today]: [],
+    [DayFlag.focusable]: []
   };
 
-  /** Custom modifiers that are coming from the `modifiers` props */
   const custom: Record<string, CalendarDay[]> = {};
 
+  const selection: Record<SelectionState, CalendarDay[]> = {
+    [SelectionState.range_end]: [],
+    [SelectionState.range_middle]: [],
+    [SelectionState.range_start]: [],
+    [SelectionState.selected]: []
+  };
+
   for (const day of calendar.days) {
     const { date, displayMonth } = day;
 
@@ -71,39 +78,52 @@ export function ModifiersProvider({ children }: { children: ReactNode }) {
 
     const isDisabled = Boolean(disabled && dateMatchModifiers(date, disabled));
 
-    const isSelected =
-      !isDisabled &&
-      (selection.isSelected(date) ||
-        Boolean(
-          modifiers?.selected && dateMatchModifiers(date, modifiers.selected)
-        ));
-
     const isHidden =
       Boolean(hidden && dateMatchModifiers(date, hidden)) ||
       (!showOutsideDays && isOutside);
 
-    const isInteractive =
-      mode !== "default" || (mode === "default" && onDayClick !== undefined);
+    const isElementInteractive = mode || onDayClick !== undefined;
 
-    const isFocusable = isInteractive && !isDisabled && !isHidden;
+    const isFocusable = isElementInteractive && !isDisabled && !isHidden;
 
     const isToday = isSameDay(date, today);
 
-    const isStartOfRange = selection.isStartOfRange(date);
-    const isEndOfRange = selection.isEndOfRange(date);
-    const isMiddleOfRange = selection.isMiddleOfRange(date);
-
     if (isOutside) internal.outside.push(day);
     if (isDisabled) internal.disabled.push(day);
     if (isHidden) internal.hidden.push(day);
     if (isFocusable) internal.focusable.push(day);
-    if (isSelected) internal.selected.push(day);
     if (isToday) internal.today.push(day);
-    if (isStartOfRange) internal.range_start.push(day);
-    if (isEndOfRange) internal.range_end.push(day);
-    if (isMiddleOfRange) internal.range_middle.push(day);
 
-    // Now add custom modifiers
+    // Add the selection modifiers
+    if (mode === "single" && !isDisabled) {
+      if (single.isSelected(day.date)) {
+        selection[SelectionState.selected].push(day);
+      }
+    }
+    if (mode === "multiple" && !isDisabled) {
+      if (multi.isSelected(day.date)) {
+        selection[SelectionState.selected].push(day);
+      }
+    }
+
+    if (mode === "range" && !isDisabled) {
+      if (range.isSelected(day.date)) {
+        selection[SelectionState.selected].push(day);
+        if (range.selected?.from && isSameDay(day.date, range.selected.from)) {
+          if (range.selected?.to)
+            selection[SelectionState.range_start].push(day);
+        } else if (
+          range.selected?.to &&
+          isSameDay(day.date, range.selected.to)
+        ) {
+          selection[SelectionState.range_end].push(day);
+        } else if (range.selected && isDateInRange(day.date, range.selected)) {
+          selection[SelectionState.range_middle].push(day);
+        }
+      }
+    }
+
+    // Add custom modifiers
     if (modifiers) {
       Object.keys(modifiers).forEach((name) => {
         const modifierValue = modifiers?.[name];
@@ -121,57 +141,79 @@ export function ModifiersProvider({ children }: { children: ReactNode }) {
   }
 
   const getModifiers = (day: CalendarDay) => {
-    const modifiers: DayModifiers = {
-      focused: false,
-      disabled: false,
-      focusable: false,
-      hidden: false,
-      outside: false,
-      range_end: false,
-      range_middle: false,
-      range_start: false,
-      selected: false,
-      today: false
+    // Initialize all the modifiers to false
+    const dayFlags: DayFlags = {
+      [DayFlag.focused]: false,
+      [DayFlag.disabled]: false,
+      [DayFlag.focusable]: false,
+      [DayFlag.hidden]: false,
+      [DayFlag.outside]: false,
+      [DayFlag.today]: false
     };
+    const selectionStates: SelectionStates = {
+      [SelectionState.range_end]: false,
+      [SelectionState.range_middle]: false,
+      [SelectionState.range_start]: false,
+      [SelectionState.selected]: false
+    };
+    const customModifiers: CustomModifiers = {};
 
+    // Find the modifiers for the given day
     for (const name in internal) {
-      const daysWithModifier = internal[name as InternalModifier];
-      modifiers[name] = daysWithModifier.some((d) => d === day);
+      const days = internal[name as DayFlag];
+      dayFlags[name as DayFlag] = days.some((d) => d === day);
+    }
+    for (const name in selection) {
+      const days = selection[name as SelectionState];
+      selectionStates[name as SelectionState] = days.some((d) => d === day);
     }
-
     for (const name in custom) {
-      // This will override the internal modifiers with the same name, as intended
-      modifiers[name] = custom[name].some((d) => d === day);
+      customModifiers[name] = custom[name].some((d) => d === day);
     }
-    return modifiers;
-  };
 
-  const calendarModifiers: CalendarModifiers = { ...internal, ...custom };
+    return {
+      ...selectionStates,
+      ...dayFlags,
+      // custom modifiers should override all the previous ones
+      ...customModifiers
+    };
+  };
 
-  const value: ModifiersContext = {
-    calendarModifiers,
+  return {
+    dayFlags: internal,
+    customModifiers: custom,
+    selectionStates: selection,
     getModifiers
   };
-
-  return (
-    <modifiersContext.Provider value={value}>
-      {children}
-    </modifiersContext.Provider>
-  );
 }
 
 /**
- * Access the modifiers used by the calendar.
- *
- * Use this hook from the custom components passed via the `components` prop.
+ * Provide the shared props to the DayPicker components. Must be used as root of
+ * the other providers.
  *
- * @group Hooks
- * @see https://react-day-picker.js.org/advanced-guides/custom-components
+ * @private
  */
-export function useModifiers(): ModifiersContext {
-  const context = useContext(modifiersContext);
-  if (!context)
-    throw new Error(`useProps must be used within a PropsProvider.`);
+export function ModifiersContextProvider({
+  children
+}: {
+  children: ReactElement;
+}) {
+  const modifiers = useModifiers();
+
+  return (
+    <ModifiersContext.Provider value={modifiers}>
+      {children}
+    </ModifiersContext.Provider>
+  );
+}
 
-  return context;
+/** @group Contexts */
+export function useModifiersContext() {
+  const modifiersContext = useContext(ModifiersContext);
+  if (!modifiersContext) {
+    throw new Error(
+      "useModifiersContext() must be used within a ModifiersContextProvider"
+    );
+  }
+  return modifiersContext;
 }
diff --git a/src/contexts/props.tsx b/src/contexts/props.tsx
index 93563fa45..9fe016cdb 100644
--- a/src/contexts/props.tsx
+++ b/src/contexts/props.tsx
@@ -1,129 +1,125 @@
-import React, {
-  createContext,
-  PropsWithChildren,
-  useContext,
-  useId
-} from "react";
+import React from "react";
 
+import * as customComponents from "../components/custom-components";
 import { getDataAttributes } from "../helpers/getDataAttributes";
 import { getDefaultClassNames } from "../helpers/getDefaultClassNames";
 import { getFormatters } from "../helpers/getFormatters";
 import { getStartEndMonths } from "../helpers/getStartEndMonths";
 import * as defaultLabels from "../labels";
-import { V9DeprecatedProps } from "../types";
 import type {
   ClassNames,
+  CustomComponents,
   DataAttributes,
-  DayPickerProps,
   Formatters,
   Labels,
   Mode,
-  PropsBase,
-  Selected,
-  SelectHandler
+  DayPickerProps
 } from "../types";
 
+const PropsContext = React.createContext<PropsContextValue | undefined>(
+  undefined
+);
+
 /**
  * Holds the props passed to the DayPicker component, with some optional props
  * set to meaningful defaults.
  *
- * Access this context using the {@link useProps} hook.
- *
- * @template T - The {@link Mode | selection mode}. Defaults to `"default"`.
- * @template R - Whether the selection is required. Defaults to `false`.
- * @group Contexts
+ * Access this context using the {@link usePropsContext} hook.
  */
-export interface PropsContext<
-  T extends Mode = "default",
-  R extends boolean = false
-> extends Omit<PropsBase, V9DeprecatedProps> {
+export type PropsContextValue = DayPickerProps & {
+  /** The mode of the selection. */
+  mode: Mode | undefined;
+  /** The class names to add to the UI. */
   classNames: ClassNames;
-  /** The `data-*` attributes passed to `<DayPicker />`. */
+  /** The unique ID of the DayPicker instance. */
+  id: string;
+  /** The data attributes to add to the calendar. */
   dataAttributes: DataAttributes;
-  endMonth: Date | undefined;
+  /** The components used in the UI. */
+  components: CustomComponents;
+  /** The formatters used in the UI. */
   formatters: Formatters;
-  id: string;
+  /** The labels used in the UI. */
   labels: Labels;
-  max: number | undefined;
-  min: number | undefined;
-  mode: T;
+  /** The number of months displayed in the calendar. */
   numberOfMonths: number;
-  required: boolean;
-  startMonth: Date | undefined;
+  /** The date of today. */
   today: Date;
-  /** The currently selected value. */
-  selected: Selected<Mode, R> | undefined;
-  /** The default selected value. */
-  defaultSelected: Selected<Mode, R> | undefined;
-  /** The function that handles the day selection. */
-  onSelect: SelectHandler<Mode, R> | undefined;
-}
+  /** The month where the navigation starts. */
+  startMonth: Date | undefined;
+  /** The month where the navigation ends. */
+  endMonth: Date | undefined;
+};
+
+function useProps(initialProps: DayPickerProps) {
+  const reactId = React.useId();
 
-const propsContext = createContext<PropsContext<Mode, boolean> | null>(null);
+  const { startMonth, endMonth } = getStartEndMonths(initialProps);
+
+  const propsContext: PropsContextValue = {
+    ...initialProps,
+    startMonth,
+    endMonth,
+    classNames: {
+      ...getDefaultClassNames(),
+      ...initialProps.classNames
+    },
+    components: {
+      ...customComponents,
+      ...initialProps.components
+    },
+    dataAttributes: getDataAttributes(initialProps),
+    formatters: getFormatters(initialProps.formatters),
+    id: initialProps.id ?? reactId,
+    labels: {
+      ...defaultLabels,
+      ...initialProps.labels
+    },
+    numberOfMonths: initialProps.numberOfMonths ?? 1,
+    today: initialProps.today ?? new Date()
+  };
+
+  return propsContext;
+}
 
 /**
- * Provide the props to the DayPicker components. Must be used as root of the
- * other providers.
+ * Provide the shared props to the DayPicker components. Must be used as root of
+ * the other providers.
  *
  * @private
  */
-export function PropsProvider<T extends Mode, R extends boolean>(
-  props: PropsWithChildren<DayPickerProps<T, R>>
-) {
-  const reactId = useId();
-  const { startMonth, endMonth } = getStartEndMonths(props);
-  const {
-    children,
-    // Remove deprecated props
-    fromDate,
-    fromMonth,
-    fromYear,
-    toDate,
-    toMonth,
-    toYear,
-    ...restProps
-  } = props;
-
-  const context = {
-    ...restProps,
-    classNames: { ...getDefaultClassNames(), ...restProps.classNames },
-    dataAttributes: getDataAttributes(props),
-    endMonth,
-    formatters: getFormatters(props.formatters),
-    id: props.id ?? reactId,
-    labels: { ...defaultLabels, ...restProps.labels },
-    max: "max" in props ? props.max ?? undefined : undefined,
-    min: "min" in props ? props.min ?? undefined : undefined,
-    mode: props.mode ?? ("default" as Mode),
-    numberOfMonths: props.numberOfMonths ?? 1,
-    required: "required" in props ? props.required ?? false : false,
-    startMonth,
-    today: props.today ?? new Date(),
-    selected: "selected" in props ? props.selected : undefined,
-    defaultSelected:
-      "defaultSelected" in props ? props.defaultSelected : undefined,
-    onSelect: "onSelect" in props ? props.onSelect : undefined
-  };
+export function PropsContextProvider<
+  ModeType extends Mode | undefined = undefined,
+  IsRequired extends boolean = false
+>({
+  initialProps,
+  children
+}: React.PropsWithChildren<{
+  initialProps: DayPickerProps;
+}>) {
+  const propsContextValue = useProps(initialProps);
 
   return (
-    <propsContext.Provider value={context}>{children}</propsContext.Provider>
+    <PropsContext.Provider value={propsContextValue}>
+      {children}
+    </PropsContext.Provider>
   );
 }
 
 /**
- * Access to the props passed to DayPicker.
+ * Access to the props passed to `DayPicker`, with some meaningful defaults.
  *
  * Use this hook from the custom components passed via the `components` prop.
  *
- * @group Hooks
+ * @group Contexts
  * @see https://react-day-picker.js.org/advanced-guides/custom-components
  */
-export function useProps() {
-  const context = useContext(propsContext);
-  if (!context) {
+export function usePropsContext() {
+  const propsContext = React.useContext(PropsContext);
+  if (!propsContext) {
     throw new Error(
-      "useDayPicker() must be used within a `<dayPickerContext.Provider/>`."
+      "usePropsContext() must be used within a PropsContextProvider"
     );
   }
-  return context;
+  return propsContext;
 }
diff --git a/src/contexts/providers.tsx b/src/contexts/providers.tsx
new file mode 100644
index 000000000..d63e3cd6c
--- /dev/null
+++ b/src/contexts/providers.tsx
@@ -0,0 +1,48 @@
+import React from "react";
+import type { PropsWithChildren } from "react";
+
+import { MultiProvider } from "../selection/multi";
+import { RangeProvider } from "../selection/range";
+import { SingleProvider } from "../selection/single";
+import type {
+  DayPickerProps,
+  PropsMulti,
+  PropsRange,
+  PropsSingle
+} from "../types";
+
+import { CalendarContextProvider } from "./calendar";
+import { FocusContextProvider } from "./focus";
+import { ModifiersContextProvider } from "./modifiers";
+import { PropsContextProvider, usePropsContext } from "./props";
+
+export function SelectionProviders({ children }: PropsWithChildren) {
+  const props = usePropsContext();
+  return (
+    <SingleProvider {...(props as PropsSingle)}>
+      <MultiProvider {...(props as PropsMulti)}>
+        <RangeProvider {...(props as PropsRange)}>{children}</RangeProvider>
+      </MultiProvider>
+    </SingleProvider>
+  );
+}
+
+/**
+ * Provide the value for all the contexts used by DayPicker.
+ *
+ * @private
+ */
+export function ContextProviders(props: PropsWithChildren<DayPickerProps>) {
+  const { children, ...initialProps } = props;
+  return (
+    <PropsContextProvider initialProps={initialProps}>
+      <CalendarContextProvider>
+        <SelectionProviders>
+          <ModifiersContextProvider>
+            <FocusContextProvider>{children}</FocusContextProvider>
+          </ModifiersContextProvider>
+        </SelectionProviders>
+      </CalendarContextProvider>
+    </PropsContextProvider>
+  );
+}
diff --git a/src/contexts/root.tsx b/src/contexts/root.tsx
deleted file mode 100644
index f33c4e7f8..000000000
--- a/src/contexts/root.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import React from "react";
-import type { FunctionComponent, PropsWithChildren } from "react";
-
-import type { DayPickerProps, Mode } from "../types";
-
-import { CalendarProvider } from "./calendar";
-import { FocusProvider } from "./focus";
-import { ModifiersProvider } from "./modifiers";
-import { PropsProvider } from "./props";
-import { SelectionProvider } from "./selection";
-
-/**
- * Provide the value for all the contexts used by DayPicker.
- *
- * @private
- */
-export const ContextProviders: FunctionComponent<
-  PropsWithChildren<DayPickerProps<Mode, boolean>>
-> = <T extends Mode>(props: PropsWithChildren<DayPickerProps<T, boolean>>) => {
-  const { children, ...dayPickerProps } = props;
-
-  return (
-    <PropsProvider {...dayPickerProps}>
-      <CalendarProvider>
-        <SelectionProvider>
-          <ModifiersProvider>
-            <FocusProvider>{children}</FocusProvider>
-          </ModifiersProvider>
-        </SelectionProvider>
-      </CalendarProvider>
-    </PropsProvider>
-  );
-};
diff --git a/src/contexts/selection.tsx b/src/contexts/selection.tsx
deleted file mode 100644
index 122df8116..000000000
--- a/src/contexts/selection.tsx
+++ /dev/null
@@ -1,264 +0,0 @@
-import React, { createContext, useContext } from "react";
-import type { KeyboardEvent, MouseEvent, PropsWithChildren } from "react";
-
-import { addDays } from "date-fns/addDays";
-import { differenceInCalendarDays } from "date-fns/differenceInCalendarDays";
-import { isSameDay } from "date-fns/isSameDay";
-import { subDays } from "date-fns/subDays";
-
-import { useControlledValue } from "../helpers/useControlledValue";
-import type { Mode, DayModifiers, Selected } from "../types";
-import { addToRange } from "../utils/addToRange";
-import { isDateInRange } from "../utils/isDateInRange";
-import { isDateRange } from "../utils/typeguards";
-
-import { useProps } from "./props";
-
-/**
- * Provides access to the currently selected value, allows setting the selected
- * days, and provides methods to check if a day is selected.
- *
- * Access the selection context using the {@link useSelection} hook.
- *
- * @template T - The {@link Mode | selection mode}. Defaults to `"default"`.
- * @template R - Whether the selection is required. Defaults to `false`.
- * @group Contexts
- */
-export interface SelectionContext<
-  T extends Mode = "default",
-  R extends boolean = false
-> {
-  /** The currently selected value. */
-  selected: Selected<T, R>;
-  /** Set the selected days. */
-  setSelected: (
-    date: Date,
-    modifiers: DayModifiers,
-    e: MouseEvent | KeyboardEvent
-  ) => void;
-  /** Return `true` if the given day is selected. */
-  isSelected: (date: Date) => boolean;
-  /**
-   * In range selection mode, return `true` if the given day is the start of the
-   * range.
-   */
-  isStartOfRange: (date: Date) => boolean;
-  /**
-   * In range selection mode, return `true` if the given day is the end of the
-   * range.
-   */
-  isEndOfRange: (date: Date) => boolean;
-  /**
-   * In range selection mode, return `true` if the given day is in the middle of
-   * the range.
-   */
-  isMiddleOfRange: (date: Date) => boolean;
-}
-
-const contextValue: SelectionContext<Mode, boolean> = {
-  selected: undefined,
-  setSelected: () => undefined,
-  isSelected: () => false,
-  isStartOfRange: () => false,
-  isEndOfRange: () => false,
-  isMiddleOfRange: () => false
-};
-
-const selectionContext =
-  createContext<SelectionContext<Mode, boolean>>(contextValue);
-
-/** @private */
-export function SelectionProvider(providerProps: PropsWithChildren) {
-  const { required, min, max, onSelect, selected, defaultSelected, mode } =
-    useProps();
-
-  const [value, setValue] = useControlledValue(
-    defaultSelected ?? selected,
-    selected
-  );
-
-  /** Set the selected days when in "single" mode. */
-  function setSingle(
-    date: Date,
-    modifiers: DayModifiers,
-    e: MouseEvent | KeyboardEvent
-  ) {
-    let selected: Date | undefined;
-    if (modifiers.selected && !required) {
-      selected = undefined;
-    } else {
-      selected = date;
-    }
-    setValue(selected);
-    onSelect?.(selected, date, modifiers, e);
-    return selected;
-  }
-
-  /** Return `true` if the given day is selected in "single" mode. */
-  function isSingleSelected(date: Date) {
-    return Boolean(value && isSameDay(value as Date, date));
-  }
-
-  /** Set the selected days when in "multiple" mode. */
-  function setMulti(
-    date: Date,
-    modifiers: DayModifiers,
-    e: MouseEvent | KeyboardEvent
-  ) {
-    if (value !== undefined && !Array.isArray(value)) {
-      // Not a multi select
-      return;
-    }
-    let selected: Date[] = [];
-
-    if (modifiers.selected) {
-      // Date is already selected
-      if (min && value && value.length <= min) {
-        // Min value reached, do nothing
-        selected = value;
-      } else {
-        // Remove the date from the selection
-        selected =
-          value?.filter((day: Date | undefined) => {
-            return Boolean(day && date && !isSameDay(day, date));
-          }) || ([] as Date[]);
-      }
-    } else if (max !== undefined && value?.length === max) {
-      // Max value reached, reset the selection to date
-      selected = date ? [date] : [];
-    } else {
-      // Add the date to the selection
-      selected = [...(value ?? []), date];
-    }
-    setValue(selected);
-    onSelect?.(selected, date, modifiers, e);
-    return selected;
-  }
-  function isMultiSelected(date: Date) {
-    if (!Array.isArray(value)) return false;
-
-    return Boolean(value?.some((day) => isSameDay(day, date)));
-  }
-
-  /** Set the selected days when in "range" mode. */
-  function setRange(
-    date: Date,
-    modifiers: DayModifiers,
-    e: MouseEvent | KeyboardEvent
-  ) {
-    if (value !== undefined && !isDateRange(value)) {
-      return;
-    }
-    const selected = date ? addToRange(date, value) : undefined;
-
-    if (min) {
-      if (
-        selected?.from &&
-        selected.to &&
-        differenceInCalendarDays(selected.to, selected.from) <= min
-      ) {
-        selected.from = date;
-        selected.to = undefined;
-      }
-    }
-
-    if (max) {
-      if (
-        selected?.from &&
-        selected.to &&
-        differenceInCalendarDays(selected.to, selected.from) + 1 > max
-      ) {
-        selected.from = date;
-        selected.to = undefined;
-      }
-    }
-
-    setValue(selected);
-    onSelect?.(selected, date, modifiers, e); // Now TypeScript knows it's a MouseEvent
-
-    return value;
-  }
-  function isRangeSelected(date: Date) {
-    if (value !== undefined && !isDateRange(value)) {
-      // Not a range select
-      return false;
-    }
-    return Boolean(value && isDateInRange(date, value));
-  }
-
-  function setSelected(
-    date: Date,
-    modifiers: DayModifiers,
-    e: MouseEvent<Element> | KeyboardEvent<Element>
-  ) {
-    if (mode === "single") setSingle(date, modifiers, e);
-    if (mode === "multiple") setMulti(date, modifiers, e);
-    if (mode === "range") setRange(date, modifiers, e);
-  }
-
-  function isSelected(date: Date) {
-    if (mode === "single") return isSingleSelected(date);
-    if (mode === "multiple") return isMultiSelected(date);
-    if (mode === "range") return isRangeSelected(date);
-    return false;
-  }
-
-  function isStartOfRange(date: Date) {
-    if (!isDateRange(value)) return false;
-    return Boolean(value.from && isSameDay(value.from, date) && value.to);
-  }
-
-  function isEndOfRange(date: Date) {
-    if (!isDateRange(value)) return false;
-    return Boolean(value.to && isSameDay(value.to, date));
-  }
-
-  function isMiddleOfRange(date: Date) {
-    if (!isDateRange(value)) return false;
-    if (!value.from || !value.to) return false;
-    return (
-      isDateInRange(date, {
-        from: addDays(value.from, 1),
-        to: subDays(value.to, 1)
-      }) &&
-      !isStartOfRange(date) &&
-      !isEndOfRange(date)
-    );
-  }
-
-  const contextValue = {
-    selected: value,
-    setSelected,
-    isSelected,
-    isStartOfRange,
-    isEndOfRange,
-    isMiddleOfRange
-  };
-
-  return (
-    <selectionContext.Provider value={contextValue}>
-      {providerProps.children}
-    </selectionContext.Provider>
-  );
-}
-
-/**
- * Access and change the currently selected values.
- *
- * Use this hook from the custom components passed via the `components` prop.
- *
- * @template T - The {@link Mode | selection mode}. Defaults to `"default"`.
- * @template R - Whether the selection is required. Defaults to `false`.
- * @group Hooks
- * @see https://react-day-picker.js.org/advanced-guides/custom-components
- */
-export function useSelection<
-  T extends Mode = "default",
-  R extends boolean = false
->() {
-  const context = useContext(selectionContext);
-  if (!context) {
-    throw new Error(`useSelection must be used within a SelectionProvider.`);
-  }
-  return context as SelectionContext<T, R>;
-}
diff --git a/src/formatters/formatMonthDropdown.ts b/src/formatters/formatMonthDropdown.ts
index 013881eea..abe64cad6 100644
--- a/src/formatters/formatMonthDropdown.ts
+++ b/src/formatters/formatMonthDropdown.ts
@@ -6,6 +6,9 @@ import { enUS } from "date-fns/locale/en-US";
  *
  * @group Formatters
  */
-export function formatMonthDropdown(monthNumber: Month, locale = enUS): string {
-  return locale.localize?.month(monthNumber) ?? monthNumber.toString();
+export function formatMonthDropdown(
+  monthNumber: number,
+  locale = enUS
+): string {
+  return locale.localize?.month(monthNumber as Month) ?? monthNumber.toString();
 }
diff --git a/src/helpers/calculateMonthWeeks.ts b/src/helpers/calculateMonthWeeks.ts
deleted file mode 100644
index d3756354b..000000000
--- a/src/helpers/calculateMonthWeeks.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import {
-  addDays,
-  differenceInCalendarDays,
-  endOfISOWeek,
-  endOfWeek,
-  getISOWeek,
-  getWeek,
-  Locale,
-  startOfISOWeek,
-  startOfWeek
-} from "date-fns";
-
-import { MonthWeek } from "./getMonthWeeks";
-
-/** Return the weeks between two dates. */
-export function calculateMonthWeeks(
-  startDate: Date,
-  endDate: Date,
-  options?: {
-    ISOWeek?: boolean;
-    locale?: Locale;
-    weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
-    firstWeekContainsDate?: 1 | 4;
-  }
-): MonthWeek[] {
-  const toWeek = options?.ISOWeek
-    ? endOfISOWeek(endDate)
-    : endOfWeek(endDate, options);
-  const fromWeek = options?.ISOWeek
-    ? startOfISOWeek(startDate)
-    : startOfWeek(startDate, options);
-
-  const nOfDays = differenceInCalendarDays(toWeek, fromWeek);
-  const days: Date[] = [];
-
-  for (let i = 0; i <= nOfDays; i++) {
-    days.push(addDays(fromWeek, i));
-  }
-
-  const weeksInMonth = days.reduce((result: MonthWeek[], date) => {
-    const weekNumber = options?.ISOWeek
-      ? getISOWeek(date)
-      : getWeek(date, options);
-
-    const existingWeek = result.find(
-      (value) => value.weekNumber === weekNumber
-    );
-    if (existingWeek) {
-      existingWeek.dates.push(date);
-      return result;
-    }
-    result.push({
-      weekNumber,
-      dates: [date]
-    });
-    return result;
-  }, []);
-
-  return weeksInMonth;
-}
diff --git a/src/helpers/getClassNamesForModifiers.ts b/src/helpers/getClassNamesForModifiers.ts
index 7a2d2badb..c693ea302 100644
--- a/src/helpers/getClassNamesForModifiers.ts
+++ b/src/helpers/getClassNamesForModifiers.ts
@@ -1,23 +1,20 @@
-import { DayModifier } from "../UI";
-import type {
-  DayModifiers,
-  ModifiersClassNames,
-  ClassNames,
-  InternalModifier
-} from "../types";
+import { DayFlag, SelectionState } from "../UI";
+import type { ModifiersClassNames, ClassNames } from "../types";
 
 export function getClassNamesForModifiers(
-  dayModifiers: DayModifiers,
+  modifiers: Record<string, boolean>,
   modifiersClassNames: ModifiersClassNames,
   classNames: ClassNames
 ) {
-  const modifierClassNames = Object.entries(dayModifiers)
+  const modifierClassNames = Object.entries(modifiers)
     .filter(([, active]) => active === true)
     .reduce((previousValue, [key]) => {
       if (modifiersClassNames[key]) {
         previousValue.push(modifiersClassNames[key as string]);
-      } else if (classNames[DayModifier[key as InternalModifier]]) {
-        previousValue.push(classNames[DayModifier[key as InternalModifier]]);
+      } else if (classNames[DayFlag[key as DayFlag]]) {
+        previousValue.push(classNames[DayFlag[key as DayFlag]]);
+      } else if (classNames[SelectionState[key as SelectionState]]) {
+        previousValue.push(classNames[SelectionState[key as SelectionState]]);
       }
       return previousValue;
     }, [] as string[]);
diff --git a/src/helpers/getDataAttributes.tsx b/src/helpers/getDataAttributes.tsx
index 1fc3ee49c..71617ce9f 100644
--- a/src/helpers/getDataAttributes.tsx
+++ b/src/helpers/getDataAttributes.tsx
@@ -1,9 +1,9 @@
-import React from "react";
-
-import type { PropsBase } from "../types";
+import type { Mode, DayPickerProps } from "../types";
 
 /** Return the `data-` attributes from the props. */
-export function getDataAttributes(props: PropsBase): Record<string, unknown> {
+export function getDataAttributes(
+  props: DayPickerProps
+): Record<string, unknown> {
   const dataAttributes: Record<string, unknown> = {};
   Object.entries(props).forEach(([key, val]) => {
     if (key.startsWith("data-")) {
diff --git a/src/helpers/getDates.ts b/src/helpers/getDates.ts
index f0d0c9263..24ee631a2 100644
--- a/src/helpers/getDates.ts
+++ b/src/helpers/getDates.ts
@@ -8,7 +8,8 @@ import { isAfter } from "date-fns/isAfter";
 import { startOfISOWeek } from "date-fns/startOfISOWeek";
 import { startOfWeek } from "date-fns/startOfWeek";
 
-import type { DayPickerProps, Mode } from "../types";
+import { PropsContextValue } from "../contexts/props";
+import type { Mode, DayPickerProps } from "../types";
 
 /** The number of days in a month when having 6 weeks. */
 const NrOfDaysWithFixedWeeks = 42;
@@ -19,7 +20,7 @@ const NrOfDaysWithFixedWeeks = 42;
  * @param firstMonth The first month of the calendar
  * @param lastMonth The last month of the calendar
  * @param maxDate The date to end the calendar at
- * @param options Options for the calendar
+ * @param props Options for the calendar
  * @param options.ISOWeek Whether or not to use ISOWeek
  * @param options.fixedWeeks Whether or not to use fixed weeks
  * @param options.locale The locale to use
@@ -27,16 +28,16 @@ const NrOfDaysWithFixedWeeks = 42;
  */
 export function getDates(
   displayMonths: Date[],
-  maxDate?: Date | undefined,
-  options?: Pick<
-    DayPickerProps<Mode, boolean>,
+  maxDate: Date | undefined,
+  props: Pick<
+    PropsContextValue,
     "ISOWeek" | "fixedWeeks" | "locale" | "weekStartsOn"
   >
 ): Date[] {
   const firstMonth = displayMonths[0];
   const lastMonth = displayMonths[displayMonths.length - 1];
 
-  const { ISOWeek, fixedWeeks, locale, weekStartsOn } = options ?? {};
+  const { ISOWeek, fixedWeeks, locale, weekStartsOn } = props ?? {};
 
   const startWeekFirstDate = ISOWeek
     ? startOfISOWeek(firstMonth)
diff --git a/src/helpers/getDefaultClassNames.ts b/src/helpers/getDefaultClassNames.ts
index 61f3ae02c..96291d106 100644
--- a/src/helpers/getDefaultClassNames.ts
+++ b/src/helpers/getDefaultClassNames.ts
@@ -1,9 +1,10 @@
 import {
   UI,
-  DayModifier,
+  DayFlag,
   CalendarFlag,
   ChevronFlag,
-  WeekNumberFlag
+  WeekNumberFlag,
+  SelectionState
 } from "../UI";
 import type { ClassNames } from "../types";
 
@@ -35,9 +36,14 @@ export function getDefaultClassNames(): Required<ClassNames> {
       `rdp-${WeekNumberFlag[key as keyof typeof WeekNumberFlag]}`;
   }
 
-  for (const key in DayModifier) {
-    classNames[DayModifier[key as keyof typeof DayModifier]] =
-      `rdp-${DayModifier[key as keyof typeof DayModifier]}`;
+  for (const key in DayFlag) {
+    classNames[DayFlag[key as keyof typeof DayFlag]] =
+      `rdp-${DayFlag[key as keyof typeof DayFlag]}`;
+  }
+
+  for (const key in SelectionState) {
+    classNames[SelectionState[key as keyof typeof SelectionState]] =
+      `rdp-${SelectionState[key as keyof typeof SelectionState]}`;
   }
 
   return classNames as Required<ClassNames>;
diff --git a/src/helpers/getDisplayMonths.ts b/src/helpers/getDisplayMonths.ts
index 9adb67054..1e7bc78eb 100644
--- a/src/helpers/getDisplayMonths.ts
+++ b/src/helpers/getDisplayMonths.ts
@@ -1,18 +1,15 @@
 import { addMonths } from "date-fns/addMonths";
 
+import type { PropsContextValue } from "../contexts/props";
+
 export function getDisplayMonths(
-  firstMonth: Date,
-  options: {
-    numberOfMonths: number;
-    toDate?: Date;
-    reverseMonths?: boolean;
-  }
+  firstDisplayedMonth: Date,
+  props: Pick<PropsContextValue, "numberOfMonths" | "endMonth">
 ) {
-  const { numberOfMonths, toDate } = options;
   const months: Date[] = [];
-  for (let i = 0; i < numberOfMonths; i++) {
-    const month = addMonths(firstMonth, i);
-    if (toDate && month > toDate) {
+  for (let i = 0; i < props.numberOfMonths; i++) {
+    const month = addMonths(firstDisplayedMonth, i);
+    if (props.endMonth && month > props.endMonth) {
       break;
     }
     months.push(month);
diff --git a/src/helpers/getDropdownMonths.ts b/src/helpers/getDropdownMonths.ts
index e2bb1e0ca..5729300d2 100644
--- a/src/helpers/getDropdownMonths.ts
+++ b/src/helpers/getDropdownMonths.ts
@@ -1,26 +1,28 @@
-import type { Month } from "date-fns";
+import type { Locale, Month } from "date-fns";
 import { addMonths } from "date-fns/addMonths";
 import { isBefore } from "date-fns/isBefore";
 import { startOfMonth } from "date-fns/startOfMonth";
 
 import { DropdownOption } from "../components/Dropdown";
-import { PropsContext } from "../contexts/props";
-import { Formatters, Mode } from "../types";
+import { PropsContextValue } from "../contexts/props";
 
 /** Return the months to show in the dropdown. */
 export function getDropdownMonths(
+  displayMonth: Date,
   props: Pick<
-    PropsContext<Mode, boolean>,
-    "startMonth" | "endMonth" | "locale"
-  > & {
-    formatters: Pick<Formatters, "formatMonthDropdown">;
-  },
-  year?: number | undefined
+    PropsContextValue,
+    "formatters" | "locale" | "startMonth" | "endMonth"
+  >
 ): DropdownOption[] | undefined {
-  if (!props.startMonth) return undefined;
-  if (!props.endMonth) return undefined;
-  const navStartMonth = startOfMonth(props.startMonth);
-  const navEndMonth = startOfMonth(props.endMonth);
+  const { startMonth, endMonth } = props;
+
+  if (!startMonth) return undefined;
+  if (!endMonth) return undefined;
+
+  const year = displayMonth.getFullYear();
+
+  const navStartMonth = startOfMonth(startMonth);
+  const navEndMonth = startOfMonth(endMonth);
 
   const months: number[] = [];
   let month = navStartMonth;
@@ -32,17 +34,10 @@ export function getDropdownMonths(
     return a - b;
   });
   const options = sortedMonths.map((value) => {
-    const label = props.formatters.formatMonthDropdown(
-      value as Month,
-      props.locale
-    );
+    const label = props.formatters.formatMonthDropdown(value, props.locale);
     const disabled =
-      (year &&
-        props.startMonth &&
-        new Date(year, value) < startOfMonth(props.startMonth)) ||
-      (year &&
-        props.endMonth &&
-        new Date(year, value) > startOfMonth(props.endMonth)) ||
+      (startMonth && new Date(year, value) < startOfMonth(startMonth)) ||
+      (endMonth && new Date(year, value) > startOfMonth(endMonth)) ||
       false;
     return { value, label, disabled };
   });
diff --git a/src/helpers/getDropdownYears.ts b/src/helpers/getDropdownYears.ts
index ddf086301..eae0b3900 100644
--- a/src/helpers/getDropdownYears.ts
+++ b/src/helpers/getDropdownYears.ts
@@ -6,34 +6,35 @@ import { startOfMonth } from "date-fns/startOfMonth";
 import { startOfYear } from "date-fns/startOfYear";
 
 import { DropdownOption } from "../components/Dropdown";
-import type { PropsContext } from "../contexts/props";
-import type { Formatters, Mode } from "../types";
+import { PropsContextValue } from "../contexts/props";
 
 /** Return the years to show in the dropdown. */
 export function getDropdownYears(
-  props: Pick<PropsContext<Mode>, "startMonth" | "endMonth"> & {
-    formatters: Pick<Formatters, "formatYearDropdown">;
-  },
-  month?: number | undefined
+  displayMonth: Date,
+  props: Pick<
+    PropsContextValue,
+    "formatters" | "locale" | "startMonth" | "endMonth"
+  >
 ): DropdownOption[] | undefined {
-  if (!props.startMonth) return undefined;
-  if (!props.endMonth) return undefined;
-  const firstNavYear = startOfYear(props.startMonth);
-  const lastNavYear = endOfYear(props.endMonth);
+  const { startMonth, endMonth } = props;
+  if (!startMonth) return undefined;
+  if (!endMonth) return undefined;
+
+  const month = displayMonth.getMonth();
+  const firstNavYear = startOfYear(startMonth);
+  const lastNavYear = endOfYear(endMonth);
   const years: number[] = [];
+
   let year = firstNavYear;
   while (isBefore(year, lastNavYear) || isSameYear(year, lastNavYear)) {
     years.push(year.getFullYear());
     year = addYears(year, 1);
   }
+
   return years.map((value) => {
     const disabled =
-      (month &&
-        props.startMonth &&
-        new Date(value, month) < startOfMonth(props.startMonth)) ||
-      (month &&
-        props.endMonth &&
-        new Date(value, month) > startOfMonth(props.endMonth)) ||
+      (startMonth && new Date(value, month) < startOfMonth(startMonth)) ||
+      (month && endMonth && new Date(value, month) > startOfMonth(endMonth)) ||
       false;
     const label = props.formatters.formatYearDropdown(value);
     return {
diff --git a/src/helpers/getFormatters.ts b/src/helpers/getFormatters.ts
index 55111f963..eeb7a4531 100644
--- a/src/helpers/getFormatters.ts
+++ b/src/helpers/getFormatters.ts
@@ -1,8 +1,8 @@
 import * as defaultFormatters from "../formatters";
-import type { PropsBase } from "../types";
+import type { DayPickerProps } from "../types";
 
 /** Return the formatters from the props merged with the default formatters. */
-export function getFormatters(customFormatters: PropsBase["formatters"]) {
+export function getFormatters(customFormatters: DayPickerProps["formatters"]) {
   if (customFormatters?.formatMonthCaption && !customFormatters.formatCaption) {
     customFormatters.formatCaption = customFormatters.formatMonthCaption;
   }
diff --git a/src/helpers/getStartMonth.ts b/src/helpers/getInitialMonth.ts
similarity index 90%
rename from src/helpers/getStartMonth.ts
rename to src/helpers/getInitialMonth.ts
index a2c14e994..6bdd9463a 100644
--- a/src/helpers/getStartMonth.ts
+++ b/src/helpers/getInitialMonth.ts
@@ -2,14 +2,13 @@ import { addMonths } from "date-fns/addMonths";
 import { differenceInCalendarMonths } from "date-fns/differenceInCalendarMonths";
 import { startOfMonth } from "date-fns/startOfMonth";
 
-import { PropsBase } from "../types";
+import type { PropsContextValue } from "../contexts/props";
 
 /** Return the start month based on the props passed to DayPicker. */
-
-export function getStartMonth(
+export function getInitialMonth(
   props: Partial<
     Pick<
-      PropsBase,
+      PropsContextValue,
       | "fromYear"
       | "toYear"
       | "startMonth"
diff --git a/src/helpers/getMonthWeeks.ts b/src/helpers/getMonthWeeks.ts
deleted file mode 100644
index c9312b88e..000000000
--- a/src/helpers/getMonthWeeks.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import {
-  addWeeks,
-  endOfMonth,
-  getWeeksInMonth,
-  Locale,
-  startOfMonth
-} from "date-fns";
-
-import { calculateMonthWeeks } from "./calculateMonthWeeks";
-
-/** Represent a week in the month. */
-export type MonthWeek = {
-  /** The week number from the start of the year. */
-  weekNumber: number;
-  /** The dates in the week. */
-  dates: Date[];
-};
-
-/**
- * Return the weeks belonging to the given month, adding the "outside days" to
- * the first and last week.
- */
-export function getMonthWeeks(
-  month: Date,
-  options: {
-    locale: Locale;
-    useFixedWeeks?: boolean;
-    weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
-    firstWeekContainsDate?: 1 | 4;
-    ISOWeek?: boolean;
-  }
-): MonthWeek[] {
-  const weeksInMonth: MonthWeek[] = calculateMonthWeeks(
-    startOfMonth(month),
-    endOfMonth(month),
-    options
-  );
-
-  if (options?.useFixedWeeks) {
-    // Add extra weeks to the month, up to 6 weeks
-    const nrOfMonthWeeks = getWeeksInMonth(month, options);
-    if (nrOfMonthWeeks < 6) {
-      const lastWeek = weeksInMonth[weeksInMonth.length - 1];
-      const lastDate = lastWeek.dates[lastWeek.dates.length - 1];
-      const toDate = addWeeks(lastDate, 6 - nrOfMonthWeeks);
-      const extraWeeks = calculateMonthWeeks(
-        addWeeks(lastDate, 1),
-        toDate,
-        options
-      );
-      weeksInMonth.push(...extraWeeks);
-    }
-  }
-  return weeksInMonth;
-}
diff --git a/src/helpers/getMonths.ts b/src/helpers/getMonths.ts
index 704d8c8e2..97972bfee 100644
--- a/src/helpers/getMonths.ts
+++ b/src/helpers/getMonths.ts
@@ -8,7 +8,7 @@ import { startOfISOWeek } from "date-fns/startOfISOWeek";
 import { startOfWeek } from "date-fns/startOfWeek";
 
 import { CalendarWeek, CalendarDay, CalendarMonth } from "../classes";
-import type { DayPickerProps, Mode } from "../types";
+import type { PropsContextValue } from "../contexts/props";
 
 /** Return the months to display in the calendar. */
 export function getMonths(
@@ -16,30 +16,31 @@ export function getMonths(
   displayMonths: Date[],
   /** The dates to display in the calendar. */
   dates: Date[],
-  options: Pick<
-    DayPickerProps<Mode, boolean>,
+  /** Options from the props context. */
+  props: Pick<
+    PropsContextValue,
     | "fixedWeeks"
     | "ISOWeek"
     | "locale"
     | "weekStartsOn"
     | "reverseMonths"
     | "firstWeekContainsDate"
-  > = {}
+  >
 ): CalendarMonth[] {
   const dayPickerMonths = displayMonths.reduce<CalendarMonth[]>(
     (months, month) => {
-      const firstDateOfFirstWeek = options.ISOWeek
+      const firstDateOfFirstWeek = props.ISOWeek
         ? startOfISOWeek(month)
         : startOfWeek(month, {
-            locale: options.locale,
-            weekStartsOn: options.weekStartsOn
+            locale: props.locale,
+            weekStartsOn: props.weekStartsOn
           });
 
-      const lastDateOfLastWeek = options.ISOWeek
+      const lastDateOfLastWeek = props.ISOWeek
         ? endOfISOWeek(endOfMonth(month))
         : endOfWeek(endOfMonth(month), {
-            locale: options.locale,
-            weekStartsOn: options.weekStartsOn
+            locale: props.locale,
+            weekStartsOn: props.weekStartsOn
           });
 
       /** The dates to display in the month. */
@@ -47,7 +48,7 @@ export function getMonths(
         return date >= firstDateOfFirstWeek && date <= lastDateOfLastWeek;
       });
 
-      if (options.fixedWeeks && monthDates.length < 42) {
+      if (props.fixedWeeks && monthDates.length < 42) {
         const extraDates = dates.filter((date) => {
           return (
             date > lastDateOfLastWeek && date <= addDays(lastDateOfLastWeek, 7)
@@ -58,12 +59,12 @@ export function getMonths(
 
       const weeks: CalendarWeek[] = monthDates.reduce<CalendarWeek[]>(
         (weeks, date) => {
-          const weekNumber = options.ISOWeek
+          const weekNumber = props.ISOWeek
             ? getISOWeek(date)
             : getWeek(date, {
-                locale: options.locale,
-                weekStartsOn: options.weekStartsOn,
-                firstWeekContainsDate: options.firstWeekContainsDate
+                locale: props.locale,
+                weekStartsOn: props.weekStartsOn,
+                firstWeekContainsDate: props.firstWeekContainsDate
               });
           const week = weeks.find((week) => week.weekNumber === weekNumber);
 
@@ -86,7 +87,7 @@ export function getMonths(
     []
   );
 
-  if (!options.reverseMonths) {
+  if (!props.reverseMonths) {
     return dayPickerMonths;
   } else {
     return dayPickerMonths.reverse();
diff --git a/src/helpers/getNextFocus.tsx b/src/helpers/getNextFocus.tsx
index ce98e72f2..74f645ea0 100644
--- a/src/helpers/getNextFocus.tsx
+++ b/src/helpers/getNextFocus.tsx
@@ -1,15 +1,14 @@
 import React from "react";
 
 import { CalendarDay } from "../classes";
-import type { MoveFocusBy, MoveFocusDir } from "../contexts/focus";
-import type { PropsContext } from "../contexts/props";
-import type { Mode } from "../types";
+import type { PropsContextValue } from "../contexts/props";
+import type { MoveFocusBy, MoveFocusDir, Mode } from "../types";
 import { dateMatchModifiers } from "../utils/dateMatchModifiers";
 
 import { getPossibleFocusDate } from "./getPossibleFocusDate";
 
 export type Options = Pick<
-  PropsContext<Mode, boolean>,
+  PropsContextValue,
   | "modifiers"
   | "locale"
   | "ISOWeek"
@@ -24,7 +23,7 @@ export function getNextFocus(
   /** The date that is currently focused. */
   focused: CalendarDay,
   options: Pick<
-    PropsContext<Mode, boolean>,
+    PropsContextValue,
     | "disabled"
     | "hidden"
     | "modifiers"
diff --git a/src/helpers/getNextMonth.ts b/src/helpers/getNextMonth.ts
index 0dc000996..038b0a591 100644
--- a/src/helpers/getNextMonth.ts
+++ b/src/helpers/getNextMonth.ts
@@ -2,6 +2,8 @@ import { addMonths } from "date-fns/addMonths";
 import { differenceInCalendarMonths } from "date-fns/differenceInCalendarMonths";
 import { startOfMonth } from "date-fns/startOfMonth";
 
+import { PropsContextValue } from "../contexts/props";
+
 /**
  * Return the next month the user can navigate to according to the given
  * options.
@@ -12,28 +14,31 @@ import { startOfMonth } from "date-fns/startOfMonth";
  * - If the navigation is paged , is the number of months displayed ahead.
  */
 export function getNextMonth(
-  firstMonth: Date,
-  options: {
-    numberOfMonths?: number;
-    startMonth: Date | undefined;
-    endMonth: Date | undefined;
-    pagedNavigation?: boolean;
-    today?: Date;
-    disableNavigation?: boolean;
-  }
+  firstDisplayedMonth: Date,
+  props: Pick<
+    PropsContextValue,
+    | "startMonth"
+    | "endMonth"
+    | "numberOfMonths"
+    | "pagedNavigation"
+    | "disableNavigation"
+  >
 ): Date | undefined {
-  if (options.disableNavigation) {
+  if (props.disableNavigation) {
     return undefined;
   }
-  const { endMonth, pagedNavigation, numberOfMonths = 1 } = options;
+  const { pagedNavigation, numberOfMonths } = props;
   const offset = pagedNavigation ? numberOfMonths : 1;
-  const month = startOfMonth(firstMonth);
+  const month = startOfMonth(firstDisplayedMonth);
 
-  if (!endMonth) {
+  if (!props.endMonth) {
     return addMonths(month, offset);
   }
 
-  const monthsDiff = differenceInCalendarMonths(endMonth, firstMonth);
+  const monthsDiff = differenceInCalendarMonths(
+    props.endMonth,
+    firstDisplayedMonth
+  );
 
   if (monthsDiff < numberOfMonths) {
     return undefined;
diff --git a/src/helpers/getPossibleFocusDate.ts b/src/helpers/getPossibleFocusDate.ts
index f57e6d5ee..3170b7002 100644
--- a/src/helpers/getPossibleFocusDate.ts
+++ b/src/helpers/getPossibleFocusDate.ts
@@ -9,9 +9,9 @@ import { min } from "date-fns/min";
 import { startOfISOWeek } from "date-fns/startOfISOWeek";
 import { startOfWeek } from "date-fns/startOfWeek";
 
-import type { MoveFocusBy, MoveFocusDir } from "../contexts/focus";
-import type { PropsContext } from "../contexts/props";
-import type { Mode } from "../types";
+import type { PropsContextValue } from "../contexts/props";
+import type { MoveFocusBy, MoveFocusDir } from "../types";
+import { Mode } from "../types";
 
 /** Return the next date that should be focused. */
 export function getPossibleFocusDate(
@@ -19,7 +19,7 @@ export function getPossibleFocusDate(
   moveDir: MoveFocusDir,
   focusedDate: Date,
   options: Pick<
-    PropsContext,
+    PropsContextValue,
     "locale" | "ISOWeek" | "weekStartsOn" | "startMonth" | "endMonth"
   >
 ): Date {
diff --git a/src/helpers/getPreviousMonth.ts b/src/helpers/getPreviousMonth.ts
index 836b69275..268ba8209 100644
--- a/src/helpers/getPreviousMonth.ts
+++ b/src/helpers/getPreviousMonth.ts
@@ -2,6 +2,8 @@ import { addMonths } from "date-fns/addMonths";
 import { differenceInCalendarMonths } from "date-fns/differenceInCalendarMonths";
 import { startOfMonth } from "date-fns/startOfMonth";
 
+import type { PropsContextValue } from "../contexts/props";
+
 /**
  * Return the next previous the user can navigate to, according to the given
  * options.
@@ -14,25 +16,21 @@ import { startOfMonth } from "date-fns/startOfMonth";
  */
 export function getPreviousMonth(
   firstDisplayedMonth: Date,
-  options: {
-    numberOfMonths?: number;
-    startMonth: Date | undefined;
-    endMonth: Date | undefined;
-    pagedNavigation?: boolean;
-    today?: Date;
-    disableNavigation?: boolean;
-  }
+  props: Pick<
+    PropsContextValue,
+    "startMonth" | "numberOfMonths" | "pagedNavigation" | "disableNavigation"
+  >
 ): Date | undefined {
-  if (options.disableNavigation) {
+  if (props.disableNavigation) {
     return undefined;
   }
-  const { startMonth, pagedNavigation, numberOfMonths = 1 } = options;
+  const { pagedNavigation, numberOfMonths } = props;
   const offset = pagedNavigation ? numberOfMonths : 1;
   const month = startOfMonth(firstDisplayedMonth);
-  if (!startMonth) {
+  if (!props.startMonth) {
     return addMonths(month, -offset);
   }
-  const monthsDiff = differenceInCalendarMonths(month, startMonth);
+  const monthsDiff = differenceInCalendarMonths(month, props.startMonth);
 
   if (monthsDiff <= 0) {
     return undefined;
diff --git a/src/helpers/getStartEndMonths.ts b/src/helpers/getStartEndMonths.ts
index 264ee133c..184e219af 100644
--- a/src/helpers/getStartEndMonths.ts
+++ b/src/helpers/getStartEndMonths.ts
@@ -5,7 +5,7 @@ import { startOfDay } from "date-fns/startOfDay";
 import { startOfMonth } from "date-fns/startOfMonth";
 import { startOfYear } from "date-fns/startOfYear";
 
-import type { DayPickerProps, Mode } from "../types";
+import type { Mode, DayPickerProps } from "../types";
 
 /**
  * Return the `fromMonth` and `toMonth` prop values values parsing the DayPicker
@@ -13,7 +13,7 @@ import type { DayPickerProps, Mode } from "../types";
  */
 export function getStartEndMonths(
   props: Pick<
-    DayPickerProps<Mode, boolean>,
+    DayPickerProps,
     | "startMonth"
     | "endMonth"
     | "today"
@@ -24,7 +24,7 @@ export function getStartEndMonths(
     | "fromMonth"
     | "toMonth"
   >
-): Pick<DayPickerProps<Mode, boolean>, "startMonth" | "endMonth"> {
+): Pick<DayPickerProps, "startMonth" | "endMonth"> {
   let { startMonth, endMonth } = props;
 
   // Handle deprecated code
diff --git a/src/helpers/getStyleForModifiers.ts b/src/helpers/getStyleForModifiers.ts
index 497633d5d..73bc6b435 100644
--- a/src/helpers/getStyleForModifiers.ts
+++ b/src/helpers/getStyleForModifiers.ts
@@ -1,10 +1,10 @@
 import type { CSSProperties } from "react";
 
 import { UI } from "../UI";
-import type { DayModifiers, ModifiersStyles, Styles } from "../types";
+import type { Modifiers, ModifiersStyles, Styles } from "../types";
 
 export function getStyleForModifiers(
-  dayModifiers: DayModifiers,
+  dayModifiers: Modifiers,
   modifiersStyles: Partial<ModifiersStyles>,
   styles: Partial<Styles>
 ): CSSProperties {
diff --git a/src/index.ts b/src/index.ts
index 6ea5bb36d..3a113307d 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,16 +1,19 @@
 export * from "./DayPicker";
+export * from "./types";
 
 export * from "./classes";
 export * from "./components/custom-components";
+
 export * from "./contexts/calendar";
 export * from "./contexts/focus";
-export * from "./contexts/props";
-export * from "./contexts/selection";
 export * from "./contexts/modifiers";
+export * from "./contexts/props";
+export * from "./selection/single";
+export * from "./selection/multi";
+export * from "./selection/range";
+
 export * from "./formatters";
 export * from "./helpers";
 export * from "./labels";
-export * from "./types-deprecated";
-export * from "./types";
 export * from "./utils";
 export * from "./UI";
diff --git a/src/labels/labelDay.ts b/src/labels/labelDay.ts
index 19a709ed5..e5e6c4c47 100644
--- a/src/labels/labelDay.ts
+++ b/src/labels/labelDay.ts
@@ -1,6 +1,6 @@
 import { format } from "date-fns/format";
 
-import type { DayModifiers } from "../types";
+import type { Modifiers } from "../types";
 
 /**
  * Return an ARIA label for the day button. By default, it returns an empty
@@ -13,7 +13,7 @@ import type { DayModifiers } from "../types";
  */
 export function labelDay(
   date: Date,
-  modifiers: DayModifiers,
+  modifiers: Modifiers,
   options: Parameters<typeof format>[2]
 ) {
   return "";
diff --git a/src/selection/multi.tsx b/src/selection/multi.tsx
new file mode 100644
index 000000000..b52df6546
--- /dev/null
+++ b/src/selection/multi.tsx
@@ -0,0 +1,110 @@
+import React from "react";
+
+import { isSameDay } from "date-fns/isSameDay";
+
+import type { Modifiers, PropsMulti } from "../types";
+
+export type MultiContextValue<T> = {
+  setSelected: (
+    triggerDate: Date,
+    modifiers: Modifiers,
+    e: React.MouseEvent | React.KeyboardEvent
+  ) => Date[] | undefined;
+  isSelected: (date: Date) => boolean;
+} & (T extends { required: true }
+  ? {
+      selected: Date[];
+    }
+  : {
+      selected: Date[] | undefined;
+    });
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const MultiContext = React.createContext<MultiContextValue<any> | undefined>(
+  undefined
+);
+
+function useMulti<T extends PropsMulti>({
+  required = false,
+  min = undefined,
+  max = undefined,
+  selected,
+  onSelect
+}: T): MultiContextValue<T> {
+  const [dates, setDates] = React.useState<Date[] | undefined>(selected);
+
+  // Update the selected date if the required flag is set.
+  React.useEffect(() => {
+    if (required && dates === undefined) setDates([new Date()]);
+  }, [required, dates]);
+
+  // Update the selected date if the initialDates changes.
+  React.useEffect(() => {
+    if (selected) setDates(selected);
+  }, [selected]);
+
+  const isSelected = (date: Date) =>
+    dates?.some((d) => isSameDay(d, date)) ?? false;
+
+  const setSelected = (
+    triggerDate: Date,
+    modifiers: Modifiers,
+    e: React.MouseEvent | React.KeyboardEvent
+  ) => {
+    let newDates = [...(dates ?? [])];
+    if (isSelected(triggerDate)) {
+      if (dates?.length === min) {
+        // Min value reached, do nothing
+        return;
+      }
+      if (required && dates?.length === 1) {
+        // Required value already selected do nothing
+        return;
+      }
+      const newDates = dates?.filter((d) => !isSameDay(d, triggerDate));
+      setDates(newDates);
+    } else {
+      if (dates?.length === max) {
+        // Max value reached, reset the selection to date
+        newDates = [triggerDate];
+      } else {
+        // Add the date to the selection
+        newDates = [...newDates, triggerDate];
+      }
+    }
+    onSelect?.(newDates, triggerDate, modifiers, e);
+    setDates(newDates);
+    return newDates;
+  };
+
+  return {
+    selected: dates,
+    setSelected,
+    isSelected
+  } as MultiContextValue<T>;
+}
+
+/** @private */
+export function MultiProvider(props: React.PropsWithChildren<PropsMulti>) {
+  const value = useMulti(props);
+  return (
+    <MultiContext.Provider value={value}>
+      {props.children}
+    </MultiContext.Provider>
+  );
+}
+
+/**
+ * Access to the multi context to get the selected dates or update them.
+ *
+ * @group Contexts
+ */
+export function useMultiContext<T extends { required: boolean }>() {
+  const context = React.useContext(MultiContext);
+  if (!context) {
+    throw new Error(
+      "useMultiContext must be used within a MultiContextProvider."
+    );
+  }
+  return context as MultiContextValue<T>;
+}
diff --git a/src/selection/range.tsx b/src/selection/range.tsx
new file mode 100644
index 000000000..416e02086
--- /dev/null
+++ b/src/selection/range.tsx
@@ -0,0 +1,117 @@
+import React from "react";
+
+import { differenceInCalendarDays } from "date-fns/differenceInCalendarDays";
+
+import { DateRange, Modifiers, PropsRange } from "../types";
+import { addToRange, isDateRange } from "../utils";
+import { isDateInRange } from "../utils/isDateInRange";
+
+export type RangeContextValue<T> = {
+  setSelected: (
+    triggerDate: Date,
+    modifiers: Modifiers,
+    e: React.MouseEvent | React.KeyboardEvent
+  ) => DateRange | undefined;
+  isSelected: (date: Date) => boolean;
+} & (T extends { required: true }
+  ? {
+      selected: DateRange;
+    }
+  : {
+      selected: DateRange | undefined;
+    });
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const RangeContext = React.createContext<RangeContextValue<any> | undefined>(
+  undefined
+);
+
+function useRange<T extends PropsRange>({
+  required,
+  min,
+  max,
+  selected,
+  onSelect
+}: T): RangeContextValue<T> {
+  const [range, setRange] = React.useState<DateRange | undefined>(selected);
+
+  // Update the selected date if the required flag is set.
+  React.useEffect(() => {
+    if (required && range === undefined)
+      setRange({ from: undefined, to: undefined });
+  }, [required, range]);
+
+  // Update the selected date if the selected changes.
+  React.useEffect(() => {
+    if (selected) setRange(selected);
+  }, [selected]);
+
+  const isSelected = required
+    ? (date: Date) => isDateInRange(date, range as DateRange)
+    : (date: Date) => range && isDateInRange(date, range);
+
+  const setSelected = (
+    triggerDate: Date,
+    modifiers: Modifiers,
+    e: React.MouseEvent | React.KeyboardEvent
+  ) => {
+    const newRange = addToRange(triggerDate, range);
+
+    if (min) {
+      if (
+        newRange?.from &&
+        newRange.to &&
+        differenceInCalendarDays(newRange.to, newRange.from) <= min
+      ) {
+        newRange.from = triggerDate;
+        newRange.to = undefined;
+      }
+    }
+
+    if (max) {
+      if (
+        newRange?.from &&
+        newRange.to &&
+        differenceInCalendarDays(newRange.to, newRange.from) + 1 > max
+      ) {
+        newRange.from = triggerDate;
+        newRange.to = undefined;
+      }
+    }
+
+    setRange(newRange);
+    onSelect?.(newRange, triggerDate, modifiers, e);
+    return newRange;
+  };
+
+  return {
+    selected: range,
+    setSelected,
+    isSelected
+  } as RangeContextValue<T>;
+}
+
+/** @private */
+export function RangeProvider(props: React.PropsWithChildren<PropsRange>) {
+  const value = useRange(props);
+  return (
+    <RangeContext.Provider value={value}>
+      {props.children}
+    </RangeContext.Provider>
+  );
+}
+
+/**
+ * Access to the range context to get the selected range or update it.
+ *
+ * @group Contexts
+ */
+export function useRangeContext<T extends { required: boolean }>() {
+  const context = React.useContext(RangeContext);
+  if (!context) {
+    throw new Error(
+      "useRangeContext() must be used within a RangeContextProvider."
+    );
+  }
+  return context as RangeContextValue<T>;
+}
diff --git a/src/selection/single.tsx b/src/selection/single.tsx
new file mode 100644
index 000000000..cb862f7f7
--- /dev/null
+++ b/src/selection/single.tsx
@@ -0,0 +1,84 @@
+import React from "react";
+
+import { isSameDay } from "date-fns/isSameDay";
+
+import { Modifiers, PropsSingle } from "../types";
+
+export type SingleContextValue<T> = {
+  setSelected: (
+    triggerDate: Date,
+    modifiers: Modifiers,
+    e: React.MouseEvent | React.KeyboardEvent
+  ) => void;
+  isSelected: (date: Date) => boolean;
+} & (T extends { required: true }
+  ? {
+      selected: Date;
+    }
+  : {
+      selected: Date | undefined;
+    });
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const SingleContext = React.createContext<SingleContextValue<any> | undefined>(
+  undefined
+);
+
+function useSingle<T extends PropsSingle>({
+  required = false,
+  selected,
+  onSelect
+}: T): SingleContextValue<T> {
+  const [date, setDate] = React.useState<Date | undefined>(selected);
+
+  // Update the selected date if the required flag is set.
+  React.useEffect(() => {
+    if (required && date === undefined) setDate(new Date());
+  }, [required, date]);
+
+  // Update the selected date if the selected changes.
+  React.useEffect(() => {
+    if (selected) setDate(selected);
+  }, [selected]);
+
+  const isSelected = (compareDate: Date) =>
+    date ? isSameDay(date, compareDate) : false;
+
+  const setSelected = (
+    triggerDate: Date,
+    modifiers: Modifiers,
+    e: React.MouseEvent | React.KeyboardEvent
+  ) => {
+    let newDate: Date | undefined = triggerDate;
+    if (!required && date && date && isSameDay(triggerDate, date)) {
+      // If the date is the same, clear the selection.
+      newDate = undefined;
+    }
+    setDate(newDate);
+    onSelect?.(newDate, triggerDate, modifiers, e);
+    return newDate;
+  };
+
+  return { selected: date, setSelected, isSelected } as SingleContextValue<T>;
+}
+
+/** @private */
+export function SingleProvider(props: React.PropsWithChildren<PropsSingle>) {
+  const value = useSingle(props);
+  return (
+    <SingleContext.Provider value={value}>
+      {props.children}
+    </SingleContext.Provider>
+  );
+}
+
+/** @group Contexts */
+export function useSingleContext() {
+  const context = React.useContext(SingleContext);
+  if (!context) {
+    throw new Error(
+      "useSingleContext must be used within a SingleContextProvider"
+    );
+  }
+  return context;
+}
diff --git a/src/types.ts b/src/types.ts
deleted file mode 100644
index b521bedcd..000000000
--- a/src/types.ts
+++ /dev/null
@@ -1,699 +0,0 @@
-import type {
-  MouseEvent,
-  FocusEvent,
-  KeyboardEvent,
-  TouchEvent,
-  PointerEvent,
-  CSSProperties,
-  ReactNode
-} from "react";
-
-import type { Locale } from "date-fns";
-
-import {
-  UI,
-  DayModifier,
-  CalendarFlag,
-  ChevronFlag,
-  WeekNumberFlag
-} from "./UI";
-import { CalendarDay } from "./classes";
-import * as components from "./components/custom-components";
-import {
-  formatCaption,
-  formatDay,
-  formatMonthCaption,
-  formatMonthDropdown,
-  formatWeekdayName,
-  formatWeekNumber,
-  formatYearCaption,
-  formatYearDropdown
-} from "./formatters";
-import {
-  labelDay,
-  labelCaption,
-  labelMonthDropdown,
-  labelNext,
-  labelPrevious,
-  labelWeekday,
-  labelWeekNumber,
-  labelWeekNumberHeader,
-  labelYearDropdown
-} from "./labels";
-
-/**
- * The props for the {@link DayPicker} component.
- *
- * @template T - The selection mode. Defaults to `"default"`.
- * @template R - Whether the selection is required. Defaults to `false`.
- * @group Props
- */
-export type DayPickerProps<
-  T extends Mode = "default",
-  R extends boolean = false
-> = PropsBase & {
-  /** The selection mode. */
-  mode?: T | undefined;
-  /** Makes the selection required. */
-  required?: R | undefined;
-} & {
-    /** Props for the single selection mode. */
-    single: PropsSingle<R>;
-    /** Props for the multi selection mode. */
-    multiple: PropsMulti<R>;
-    /** Props for the range selection mode. */
-    range: PropsRange<R>;
-    default: object;
-  }[T];
-
-/**
- * Selection modes supported by DayPicker.
- *
- * - `single`: use DayPicker to select single days.
- * - `multiple`: allow selecting multiple days.
- * - `range`: use DayPicker to select a range of days
- * - `default`: disable any built-in selection behavior. Customize what is
- *   selected by using `onDayClick` and `modifiers`.
- *
- * @see https://react-day-picker.js.org/next/using-daypicker/selection-modes
- */
-export type Mode = "single" | "multiple" | "range" | "default";
-
-/**
- * Props to change the navigation, the styling and the behavior of the calendar.
- *
- * @group Props
- */
-export interface PropsBase {
-  /** Class name to add to the root element */
-  className?: string;
-  /**
-   * Change the class names used by DayPicker.
-   *
-   * Use this prop when you need to change the default class names — for example
-   * when importing the style via CSS modules or when using a CSS framework.
-   */
-  classNames?: Partial<ClassNames>;
-  /** Change the class name for the day matching the `modifiers`. */
-  modifiersClassNames?: ModifiersClassNames;
-  /** Style to apply to the root element. */
-  style?: CSSProperties;
-  /** Change the inline styles of the HTML elements. */
-  styles?: Partial<Styles>;
-  /** Change the class name for the day matching the {@link modifiers}. */
-  modifiersStyles?: ModifiersStyles;
-  /**
-   * A unique id to replace the random generated ids – used by DayPicker for
-   * accessibility.
-   */
-  id?: string;
-  /**
-   * The initial month to show in the calendar.
-   *
-   * Use this prop to let DayPicker control the current month. If you need to
-   * set the month programmatically, use {@link month} and {@link onMonthChange}.
-   *
-   * @defaultValue The current month
-   */
-  defaultMonth?: Date;
-  /**
-   * The month displayed in the calendar.
-   *
-   * As opposed to `PropsBase.defaultMonth`, use this prop with
-   * `PropsBase.onMonthChange} to change the month programmatically.
-   */
-  month?: Date;
-  /**
-   * The number of displayed months.
-   *
-   * @defaultValue 1
-   */
-  numberOfMonths?: number;
-  /**
-   * The earliest month to start the month navigation.
-   *
-   * @since 9.0.0
-   */
-  startMonth?: Date | undefined;
-  /**
-   * @private
-   * @deprecated This prop has been removed. Use `hidden={{ before: date }}`
-   *   instead.
-   */
-  fromDate?: Date | undefined;
-  /**
-   * @private
-   * @deprecated This prop has been renamed to `startMonth`.
-   */
-  fromMonth?: Date | undefined;
-  /**
-   * @private
-   * @deprecated Use `startMonth` instead. E.g. `startMonth={new Date(year,
-   *   0)}`.
-   */
-  fromYear?: number | undefined;
-
-  /**
-   * The latest month to end the month navigation.
-   *
-   * @since 9.0.0
-   */
-  endMonth?: Date;
-  /**
-   * @private
-   * @deprecated This prop has been removed. Use `hidden={{ after: date }}`
-   *   instead.
-   */
-  toDate?: Date;
-  /**
-   * @private
-   * @deprecated This prop has been renamed to `endMonth`.
-   */
-  toMonth?: Date;
-  /**
-   * @private
-   * @deprecated Use `endMonth` instead. E.g. `endMonth={new Date(year, 0)}`.
-   */
-  toYear?: number;
-
-  /** Paginate the month navigation displaying the `numberOfMonths` at time. */
-  pagedNavigation?: boolean;
-  /**
-   * Render the months in reversed order (when {@link numberOfMonths} is set) to
-   * display the most recent month first.
-   */
-  reverseMonths?: boolean;
-  /**
-   * Hide the navigation buttons. This prop won't disable the navigation: to
-   * disable the navigation, use {@link disableNavigation}.
-   *
-   * @since 9.0.0
-   */
-  hideNavigation?: boolean;
-  /**
-   * Disable the navigation between months. This prop won't hide the navigation:
-   * to hide the navigation, use {@link hideNavigation}.
-   */
-  disableNavigation?: boolean;
-  /**
-   * Show dropdowns to navigate between months or years.
-   *
-   * - `true`: display the dropdowns for both month and year
-   * - `label`: display the month and the year as a label. Change the label with
-   *   the `formatCaption` formatter.
-   * - `month`: display only the dropdown for the months
-   * - `year`: display only the dropdown for the years
-   *
-   * **Note:** showing the dropdown will set the start/end months
-   * {@link fromYear} to the 100 years ago, and {@link toYear} to the current
-   * year.
-   */
-  captionLayout?: "label" | "dropdown" | "dropdown-months" | "dropdown-years";
-  /**
-   * Display always 6 weeks per each month, regardless the month’s number of
-   * weeks. Weeks will be filled with the days from the next month.
-   */
-  fixedWeeks?: boolean;
-  /**
-   * Hide the row displaying the weekday row header.
-   *
-   * @since 9.0.0
-   */
-  hideWeekdayRow?: boolean;
-  /** Show the outside days (days falling in the next or the previous month). */
-  showOutsideDays?: boolean;
-  /**
-   * Show the week numbers column. Weeks are numbered according to the local
-   * week index.
-   *
-   * - To use ISO week numbering, use the `ISOWeek` prop.
-   * - To change how the week numbers are displayed, use the `formatters` prop.
-   */
-  showWeekNumber?: boolean;
-  /**
-   * Use ISO week dates instead of the locale setting. Setting this prop will
-   * ignore `weekStartsOn` and `firstWeekContainsDate`.
-   *
-   * @see https://en.wikipedia.org/wiki/ISO_week_date
-   */
-  ISOWeek?: boolean;
-  /** Change the components used for rendering the calendar elements. */
-  components?: CustomComponents;
-  /** Content to add to the grid as footer element. */
-  footer?: ReactNode;
-  /**
-   * When a selection mode is set, DayPicker will focus the first selected day
-   * (if set) or the today's date (if not disabled).
-   *
-   * Use this prop when you need to focus DayPicker after a user actions, for
-   * improved accessibility.
-   */
-  autoFocus?: boolean;
-  /** Apply the `disabled` modifier to the matching days. */
-  disabled?: Matcher | Matcher[] | undefined;
-  /**
-   * Apply the `hidden` modifier to the matching days. Will hide them from the
-   * calendar.
-   */
-  hidden?: Matcher | Matcher[] | undefined;
-  /**
-   * The today’s date. Default is the current date. This date will get the
-   * `today` modifier to style the day.
-   */
-  today?: Date;
-  /** Add modifiers to the matching days. */
-  modifiers?: Record<string, Matcher | Matcher[] | undefined> | undefined;
-  /**
-   * Labels creators to override the defaults. Use this prop to customize the
-   * aria-label attributes in DayPicker.
-   */
-  labels?: Partial<Labels>;
-  /**
-   * Formatters used to format dates to strings. Use this prop to override the
-   * default functions.
-   */
-  formatters?: Partial<Formatters>;
-  /**
-   * The text direction of the calendar. Use `ltr` for left-to-right (default)
-   * or `rtl` for right-to-left.
-   */
-  dir?: HTMLDivElement["dir"];
-  /**
-   * A cryptographic nonce ("number used once") which can be used by Content
-   * Security Policy for the inline `style` attributes.
-   */
-  nonce?: HTMLDivElement["nonce"];
-  /** Add a `title` attribute to the container element. */
-  title?: HTMLDivElement["title"];
-  /** Add the language tag to the container element. */
-  lang?: HTMLDivElement["lang"];
-  /**
-   * The date-fns locale object used to localize dates.
-   *
-   * @defaultValue en-US
-   * @see https://date-fns.org/docs/Locale
-   */
-  locale?: Locale | undefined;
-  /**
-   * The index of the first day of the week (0 - Sunday). Overrides the locale's
-   * one.
-   */
-  weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined;
-  /**
-   * The day of January, which is always in the first week of the year.
-   *
-   * @see https://date-fns.org/docs/getWeek
-   * @see https://en.wikipedia.org/wiki/Week#Numbering
-   */
-  firstWeekContainsDate?: 1 | 4;
-  /**
-   * Enable `DD` and `DDDD` for week year tokens when formatting or parsing
-   * dates.
-   *
-   * @see https://date-fns.org/docs/Unicode-Tokens
-   */
-  useAdditionalWeekYearTokens?: boolean | undefined;
-  /**
-   * Enable `YY` and `YYYY` for day of year tokens when formatting or parsing
-   * dates.
-   *
-   * @see https://date-fns.org/docs/Unicode-Tokens
-   */
-  useAdditionalDayOfYearTokens?: boolean | undefined;
-
-  /* EVENT HANDLERS */
-  /** Event fired when the user navigates between months. */
-  onMonthChange?: MonthChangeEventHandler;
-  /** Event handler when a day is clicked. */
-  onDayClick?: DayEventHandler<MouseEvent>;
-  /** Event handler when a day is focused. */
-  onDayFocus?: DayEventHandler<FocusEvent>;
-  /** Event handler when a day is blurred. */
-  onDayBlur?: DayEventHandler<FocusEvent>;
-  /** Event handler when the mouse enters a day. */
-  onDayMouseEnter?: DayEventHandler<MouseEvent>;
-  /** Event handler when the mouse leaves a day. */
-  onDayMouseLeave?: DayEventHandler<MouseEvent>;
-  /** Event handler when a key is pressed on a day. */
-  onDayKeyDown?: DayEventHandler<KeyboardEvent>;
-  /** Event handler when a key is released on a day. */
-  onDayKeyUp?: DayEventHandler<KeyboardEvent>;
-  /** Event handler when a key is pressed and released on a day. */
-  onDayKeyPress?: DayEventHandler<KeyboardEvent>;
-  /** Event handler when a pointer enters a day. */
-  onDayPointerEnter?: DayEventHandler<PointerEvent>;
-  /** Event handler when a pointer leaves a day. */
-  onDayPointerLeave?: DayEventHandler<PointerEvent>;
-  /** Event handler when a touch is cancelled on a day. */
-  onDayTouchCancel?: DayEventHandler<TouchEvent>;
-  /** Event handler when a touch ends on a day. */
-  onDayTouchEnd?: DayEventHandler<TouchEvent>;
-  /** Event handler when a touch moves on a day. */
-  onDayTouchMove?: DayEventHandler<TouchEvent>;
-  /** Event handler when a touch starts on a day. */
-  onDayTouchStart?: DayEventHandler<TouchEvent>;
-  /** Event handler when the next month button is clicked. */
-  onNextClick?: MonthChangeEventHandler;
-  /** Event handler when the previous month button is clicked. */
-  onPrevClick?: MonthChangeEventHandler;
-  /**
-   * Event handler when a week number is clicked. Requires {@link showWeekNumber}
-   * to be set.
-   */
-  onWeekNumberClick?: WeekNumberMouseEventHandler;
-}
-
-/**
- * Props for the single selection mode, when `mode="single"`.
- *
- * @template R - Whether the selection is required. Defaults to `false`.
- * @group Props
- */
-export interface PropsSingle<R extends boolean = false> {
-  /** Makes the selection required. */
-  required?: R | undefined;
-  /** The selected Date. */
-  selected?: Selected<"single", R>;
-  /** The initially selected value when not controlled. */
-  defaultSelected?: Selected<"single", R>;
-  /** The callback called when the user selects a day. */
-  onSelect?: SelectHandler<"single", R> | undefined;
-}
-
-/**
- * Props for the multi selection mode, when `mode="multiple"`.
- *
- * @template R - Whether the selection is required. Defaults to `false`.
- * @group Props
- */
-export interface PropsMulti<R extends boolean = false> {
-  /** The selected dates. */
-  selected?: Selected<"multiple", R>;
-  /** The initially selected values when not controlled. */
-  defaultSelected?: Selected<"multiple", R>;
-  /** The callback called when the user selects a day. */
-  onSelect?: SelectHandler<"multiple", R>;
-  /** Makes the selection required. */
-  required?: R | undefined;
-  /** The minimum number of days that can be selected. */
-  min?: number | undefined;
-  /** The maximum number of days that can be selected. */
-  max?: number | undefined;
-}
-
-/**
- * Props for the range selection mode, when `mode="range"`.
- *
- * @template R - Whether the selection is required. Defaults to `false`.
- * @group Props
- */
-export interface PropsRange<R extends boolean = false> {
-  /** The selected range. */
-  selected?: Selected<"range", R>;
-  /** The initially selected range when not controlled. */
-  defaultSelected?: Selected<"range", R>;
-  /** The callback called when the user selects a day. */
-  onSelect?: SelectHandler<"range", R>;
-  /** Makes the selection required. */
-  required?: R;
-  /** The minimum number of days that can be selected. */
-  min?: number;
-  /** The maximum number of days that can be selected. */
-  max?: number;
-}
-/**
- * Props for the default selection mode, when `mode="default"`.
- *
- * @group Props
- */
-export interface PropsDefault extends PropsBase {
-  mode?: undefined | "default";
-}
-
-/**
- * The selected value when in selection mode.
- *
- * @template T - The {@link Mode | selection mode}. Defaults to `"default"`.
- * @template R - Whether the selection is required. Defaults to `false`.
- */
-export type Selected<
-  T extends Mode = "default",
-  R extends boolean = false
-> = T extends "single"
-  ? Date | (R extends true ? Date : undefined)
-  : T extends "multiple"
-    ? Date[] | (R extends true ? Date[] : undefined)
-    : T extends "range"
-      ? DateRange | (R extends true ? DateRange : undefined)
-      : undefined;
-
-/**
- * The callback called when the user select a days from the calendar. *
- *
- * @template T - The {@link Mode | selection mode}. Defaults to `"default"`.
- * @template R - Whether the selection is required. Defaults to `false`.
- */
-export type SelectHandler<
-  T extends Mode = "default",
-  R extends boolean = false
-> = (
-  /** The new selected value. */
-  selected: Selected<T, R>,
-  /** The date that triggered the selection. */
-  date: Date,
-  /** The modifiers for the day that triggered the selection. */
-  modifiers: DayModifiers,
-  /** The event that made the selection. */
-  e: MouseEvent | KeyboardEvent
-) => void;
-
-/**
- * The components that can be changed using the `components` prop.
- *
- * @see https://github.com/gpbl/react-day-picker/blob/main/src/components/custom-components.ts
- */
-export type CustomComponents = {
-  [key in keyof typeof components]?: (typeof components)[key];
-};
-
-/** Represent a map of formatters used to render localized content. */
-export type Formatters = {
-  /** Format the caption of a month grid. */
-  formatCaption: typeof formatCaption;
-  /** @deprecated Use {@link Formatters.formatCaption} instead. */
-  formatMonthCaption: typeof formatMonthCaption;
-  /** Format the label in the month dropdown. */
-  formatMonthDropdown: typeof formatMonthDropdown;
-  /** @deprecated Use {@link Formatters.formatYearDropdown} instead. */
-  formatYearCaption: typeof formatYearCaption;
-  /** Format the label in the year dropdown. */
-  formatYearDropdown: typeof formatYearDropdown;
-  /** Format the day in the day cell. */
-  formatDay: typeof formatDay;
-  /** Format the week number. */
-  formatWeekNumber: typeof formatWeekNumber;
-  /** Format the week day name in the header */
-  formatWeekdayName: typeof formatWeekdayName;
-};
-
-/** Map of functions to translate ARIA labels for the relative elements. */
-export type Labels = {
-  /** Return the label for the month dropdown. */
-  labelCaption: typeof labelCaption;
-  /** Return the label for the month dropdown. */
-  labelMonthDropdown: typeof labelMonthDropdown;
-  /** Return the label for the year dropdown. */
-  labelYearDropdown: typeof labelYearDropdown;
-  /** Return the label for the next month button. */
-  labelNext: typeof labelNext;
-  /** Return the label for the previous month button. */
-  labelPrevious: typeof labelPrevious;
-  /** Return the label for the day cell. */
-  labelDay: typeof labelDay;
-  /** Return the label for the weekday. */
-  labelWeekday: typeof labelWeekday;
-  /** Return the label for the week number. */
-  labelWeekNumber: typeof labelWeekNumber;
-  /**
-   * Return the label for the column of the week number.
-   *
-   * @since 9.0.0
-   */
-  labelWeekNumberHeader: typeof labelWeekNumberHeader;
-};
-
-/**
- * A value or a function that matches a specific day.
- *
- * Matchers can be of different types:
- *
- * ```tsx
- * // will always match the day
- * const booleanMatcher: Matcher = true;
- *
- * // will match the today's date
- * const dateMatcher: Matcher = new Date();
- *
- * // will match the days in the array
- * const arrayMatcher: Matcher = [
- *   new Date(2019, 1, 2),
- *   new Date(2019, 1, 4)
- * ];
- *
- * // will match days after the 2nd of February 2019
- * const afterMatcher: DateAfter = { after: new Date(2019, 1, 2) };
- * // will match days before the 2nd of February 2019 }
- * const beforeMatcher: DateBefore = { before: new Date(2019, 1, 2) };
- *
- * // will match Sundays
- * const dayOfWeekMatcher: DayOfWeek = {
- *   dayOfWeek: 0
- * };
- *
- * // will match the included days, except the two dates
- * const intervalMatcher: DateInterval = {
- *   after: new Date(2019, 1, 2),
- *   before: new Date(2019, 1, 5)
- * };
- *
- * // will match the included days, including the two dates
- * const rangeMatcher: DateRange = {
- *   from: new Date(2019, 1, 2),
- *   to: new Date(2019, 1, 5)
- * };
- *
- * // will match when the function return true
- * const functionMatcher: Matcher = (day: Date) => {
- *   return day.getMonth() === 2; // match when month is March
- * };
- * ```
- */
-export type Matcher =
-  | boolean
-  | ((date: Date) => boolean)
-  | Date
-  | Date[]
-  | DateRange
-  | DateBefore
-  | DateAfter
-  | DateInterval
-  | DayOfWeek;
-
-/**
- * A matcher to match a day falling after the specified date, with the date not
- * included.
- */
-export type DateAfter = { after: Date };
-
-/**
- * A matcher to match a day falling before the specified date, with the date not
- * included.
- */
-export type DateBefore = { before: Date };
-
-/**
- * A matcher to match a day falling before and/or after two dates, where the
- * dates are not included.
- */
-export type DateInterval = { before: Date; after: Date };
-
-/**
- * A matcher to match a range of dates. The range can be open. Differently from
- * {@link DateInterval}, the dates here are included.
- */
-export type DateRange = { from: Date | undefined; to?: Date | undefined };
-
-/**
- * A matcher to match a date being one of the specified days of the week (`0-6`,
- * where `0` is Sunday).
- */
-export type DayOfWeek = { dayOfWeek: number[] };
-
-/** A record with `data-*` attributes passed to `<DayPicker />`. */
-export type DataAttributes = Record<`data-${string}`, unknown>;
-
-/**
- * The event handler triggered when interacting with a day.
- *
- * @template T - The event type that triggered the day event.
- */
-export type DayEventHandler<T> = (
-  /** The date that has triggered the event. */
-  date: Date,
-  /** The modifiers belonging to the date. */
-  modifiers: DayModifiers,
-  /** The DOM event that triggered this event. */
-  e: T
-) => void;
-
-/** The event handler when a month is changed in the calendar. */
-export type MonthChangeEventHandler = (month: Date) => void;
-
-/** The event handler when the week number is clicked. */
-export type WeekNumberMouseEventHandler = (
-  /** The week number that has been clicked. */
-  weekNumber: number,
-  /** The dates in the clicked week. */
-  dates: Date[],
-  /** The mouse event that triggered this event. */
-  e: MouseEvent
-) => void;
-
-/** Maps {@link UI} elements to their CSS properties. */
-export type Styles = {
-  [uiElement in UI]: CSSProperties | undefined;
-};
-
-/**
- * Maps {@link UI}, {@link DayModifier}, {@link CalendarFlag}, {@link ChevronFlag},
- * and {@link WeekNumberFlag} elements to their class names.
- */
-export type ClassNames = {
-  [key in
-    | UI
-    | DayModifier
-    | CalendarFlag
-    | ChevronFlag
-    | WeekNumberFlag]: string;
-};
-
-/** The modifiers that are internally used by DayPicker. */
-export type InternalModifier = keyof typeof DayModifier;
-
-/** A map of all the modifiers with the calendar days. */
-export type CalendarModifiers = Record<string, CalendarDay[]> &
-  Record<InternalModifier, CalendarDay[]>;
-
-/** The modifiers that are matching the day in the calendar. */
-export type DayModifiers = Record<string, boolean> &
-  Record<InternalModifier, boolean>;
-
-/** The style to apply to each day element matching a modifier. */
-export type ModifiersStyles = Record<string, CSSProperties> &
-  Partial<Record<InternalModifier, CSSProperties>>;
-
-/** The classnames to assign to each day element matching a modifier. */
-export type ModifiersClassNames = Record<string, string> &
-  Partial<Record<InternalModifier, string>>;
-
-export type test = DayPickerProps;
-
-/**
- * The props that have been deprecated since version 9.0.0.
- *
- * @since 9.0.0
- * @see https://react-day-picker.js.org/next/upgrading
- */
-export type V9DeprecatedProps =
-  /** Use `hidden` prop instead. */
-  | "fromDate"
-  /** Use `hidden` prop instead. */
-  | "toDate"
-  /** Use `startMonth` instead. */
-  | "fromMonth"
-  /** Use `endMonth` instead. */
-  | "toMonth"
-  /** Use `startMonth` instead. */
-  | "fromYear"
-  /** Use `endMonth` instead. */
-  | "toYear";
diff --git a/src/types-deprecated.ts b/src/types/deprecated.ts
similarity index 80%
rename from src/types-deprecated.ts
rename to src/types/deprecated.ts
index dad669c21..bcdb764ef 100644
--- a/src/types-deprecated.ts
+++ b/src/types/deprecated.ts
@@ -1,19 +1,16 @@
 /* eslint-disable @typescript-eslint/no-explicit-any */
-import { Calendar } from "./components/Calendar";
-import { MonthCaption, MonthCaptionProps } from "./components/MonthCaption";
-import { Week, type WeekProps } from "./components/Week";
-import { useCalendar } from "./contexts/calendar";
-import { PropsContext, useProps } from "./contexts/props";
-import { labelDay, labelNext, labelWeekday, labelWeekNumber } from "./labels";
-import type {
-  Mode,
-  PropsSingle,
-  PropsDefault,
-  PropsMulti,
-  PropsRange,
-  SelectHandler,
-  DayEventHandler
-} from "./types";
+import { Calendar } from "../components/Calendar";
+import {
+  MonthCaption,
+  type MonthCaptionProps
+} from "../components/MonthCaption";
+import { Week, type WeekProps } from "../components/Week";
+import { useCalendarContext } from "../contexts/calendar";
+import { usePropsContext, type PropsContextValue } from "../contexts/props";
+import { labelDay, labelNext, labelWeekday, labelWeekNumber } from "../labels";
+
+import type { PropsMulti, PropsRange, PropsSingle } from "./props";
+import type { Mode, DayEventHandler } from "./shared";
 
 /**
  * @deprecated This type will be removed.
@@ -75,25 +72,25 @@ export type RowProps = WeekProps;
  * @deprecated This type has been renamed. Use `PropsSingle` instead.
  * @protected
  */
-export type DayPickerSingleProps = PropsSingle<boolean>;
+export type DayPickerSingleProps = PropsSingle;
 
 /**
  * @deprecated This type has been renamed. Use `PropsMulti` instead.
  * @protected
  */
-export type DayPickerMultipleProps = PropsMulti<boolean>;
+export type DayPickerMultipleProps = PropsMulti;
 
 /**
  * @deprecated This type has been renamed. Use `PropsRange` instead.
  * @protected
  */
-export type DayPickerRangeProps = PropsRange<boolean>;
+export type DayPickerRangeProps = PropsRange;
 
 /**
- * @deprecated This type will be removed.
+ * @deprecated This type will be removed. Use `NonNullable<unknown>` instead
  * @protected
  */
-export type DayPickerDefaultProps = PropsDefault;
+export type DayPickerDefaultProps = NonNullable<unknown>;
 
 /**
  * @deprecated This type has been renamed. Use `Mode` instead.
@@ -111,20 +108,20 @@ export type Modifier = string;
  * @deprecated This type will be removed. Use `SelectHandler<"single">` instead.
  * @protected
  */
-export type SelectSingleEventHandler = SelectHandler<"single", false>;
+export type SelectSingleEventHandler = PropsSingle["onSelect"];
 
 /**
  * @deprecated This type will be removed. Use `SelectHandler<"multiple">`
  *   instead.
  * @protected
  */
-export type SelectMultipleEventHandler = SelectHandler<"multiple", false>;
+export type SelectMultipleEventHandler = PropsMulti["onSelect"];
 
 /**
  * @deprecated This type will be removed. Use `SelectHandler<"range">` instead.
  * @protected
  */
-export type SelectRangeEventHandler = SelectHandler<"range", false>;
+export type SelectRangeEventHandler = PropsRange["onSelect"];
 
 /**
  * @deprecated This type is not used anymore.
@@ -135,21 +132,21 @@ export type DayPickerProviderProps = any;
 /**
  * @deprecated This type has been renamed to `useProps`.
  * @protected
- * @group Hooks
+ * @group Contexts
  */
-export const useDayPicker = useProps;
+export const useDayPicker = usePropsContext;
 
 /**
  * @deprecated This type has been renamed to `useProps`.
  * @protected
- * @group Hooks
+ * @group Contexts
  */
-export const useNavigation = useCalendar;
+export const useNavigation = useCalendarContext;
 
 /**
  * @deprecated This hook has been removed. Use a custom `Day` component instead.
  * @protected
- * @group Hooks
+ * @group Contexts
  * @see https://react-day-picker.js.org/advanced-guides/custom-components
  */
 export type useDayRender = any;
@@ -231,4 +228,4 @@ export type DayTouchEventHandler = DayEventHandler<React.TouchEvent>;
  * @deprecated The type has been renamed. Use `PropsContext` instead.
  * @protected
  */
-export type DayPickerContext = PropsContext;
+export type DayPickerContext = PropsContextValue;
diff --git a/src/types/index.ts b/src/types/index.ts
new file mode 100644
index 000000000..cf18a0c26
--- /dev/null
+++ b/src/types/index.ts
@@ -0,0 +1,3 @@
+export * from "./deprecated";
+export * from "./shared";
+export * from "./props";
diff --git a/src/types/props.ts b/src/types/props.ts
new file mode 100644
index 000000000..8b352ca9f
--- /dev/null
+++ b/src/types/props.ts
@@ -0,0 +1,416 @@
+/* eslint-disable @typescript-eslint/ban-types */
+import React from "react";
+
+import type { Locale } from "date-fns";
+
+import type {
+  ClassNames,
+  ModifiersClassNames,
+  Styles,
+  ModifiersStyles,
+  CustomComponents,
+  Matcher,
+  Labels,
+  Formatters,
+  MonthChangeEventHandler,
+  DayEventHandler,
+  WeekNumberMouseEventHandler,
+  Modifiers,
+  DateRange,
+  Mode
+} from "./shared";
+
+/**
+ * The props for the `<DayPicker />` component.
+ *
+ * @group Props
+ */
+export type DayPickerProps = PropsBase &
+  (
+    | PropsSingle
+    | PropsSingleRequired
+    | PropsMulti
+    | PropsMultiRequired
+    | PropsRange
+    | PropsRangeRequired
+    | { mode?: undefined }
+  );
+
+/** @group Props */
+export interface PropsBase {
+  mode?: Mode | undefined;
+  /** Class name to add to the root element */
+  className?: string;
+  /**
+   * Change the class names used by DayPicker.
+   *
+   * Use this prop when you need to change the default class names — for example
+   * when importing the style via CSS modules or when using a CSS framework.
+   */
+  classNames?: Partial<ClassNames>;
+  /** Change the class name for the day matching the `modifiers`. */
+  modifiersClassNames?: ModifiersClassNames;
+  /** Style to apply to the root element. */
+  style?: React.CSSProperties;
+  /** Change the inline styles of the HTML elements. */
+  styles?: Partial<Styles>;
+  /** Change the class name for the day matching the {@link modifiers}. */
+  modifiersStyles?: ModifiersStyles;
+  /** A unique id to replace the React generated id. Used for ARIA labels. */
+  id?: string;
+  /**
+   * The initial month to show in the calendar.
+   *
+   * Use this prop to let DayPicker control the current month. If you need to
+   * set the month programmatically, use {@link month} and {@link onMonthChange}.
+   *
+   * @defaultValue The current month
+   */
+  defaultMonth?: Date;
+  /**
+   * The month displayed in the calendar.
+   *
+   * As opposed to `defaultMonth`, use this prop with `onMonthChange` to change
+   * the month programmatically.
+   */
+  month?: Date;
+  /**
+   * The number of displayed months.
+   *
+   * @defaultValue 1
+   */
+  numberOfMonths?: number;
+  /**
+   * The earliest month to start the month navigation.
+   *
+   * @since 9.0.0
+   */
+  startMonth?: Date | undefined;
+  /**
+   * @private
+   * @deprecated This prop has been removed. Use `hidden={{ before: date }}`
+   *   instead.
+   */
+  fromDate?: Date | undefined;
+  /**
+   * @private
+   * @deprecated This prop has been renamed to `startMonth`.
+   */
+  fromMonth?: Date | undefined;
+  /**
+   * @private
+   * @deprecated Use `startMonth` instead. E.g. `startMonth={new Date(year,
+   *   0)}`.
+   */
+  fromYear?: number | undefined;
+
+  /**
+   * The latest month to end the month navigation.
+   *
+   * @since 9.0.0
+   */
+  endMonth?: Date;
+  /**
+   * @private
+   * @deprecated This prop has been removed. Use `hidden={{ after: date }}`
+   *   instead.
+   */
+  toDate?: Date;
+  /**
+   * @private
+   * @deprecated This prop has been renamed to `endMonth`.
+   */
+  toMonth?: Date;
+  /**
+   * @private
+   * @deprecated Use `endMonth` instead. E.g. `endMonth={new Date(year, 0)}`.
+   */
+  toYear?: number;
+
+  /** Paginate the month navigation displaying the `numberOfMonths` at time. */
+  pagedNavigation?: boolean;
+  /**
+   * Render the months in reversed order (when {@link numberOfMonths} is set) to
+   * display the most recent month first.
+   */
+  reverseMonths?: boolean;
+  /**
+   * Hide the navigation buttons. This prop won't disable the navigation: to
+   * disable the navigation, use {@link disableNavigation}.
+   *
+   * @since 9.0.0
+   */
+  hideNavigation?: boolean;
+  /**
+   * Disable the navigation between months. This prop won't hide the navigation:
+   * to hide the navigation, use {@link hideNavigation}.
+   */
+  disableNavigation?: boolean;
+  /**
+   * Show dropdowns to navigate between months or years.
+   *
+   * - `true`: display the dropdowns for both month and year
+   * - `label`: display the month and the year as a label. Change the label with
+   *   the `formatCaption` formatter.
+   * - `month`: display only the dropdown for the months
+   * - `year`: display only the dropdown for the years
+   *
+   * **Note:** showing the dropdown will set the start/end months
+   * {@link fromYear} to the 100 years ago, and {@link toYear} to the current
+   * year.
+   */
+  captionLayout?: "label" | "dropdown" | "dropdown-months" | "dropdown-years";
+  /**
+   * Display always 6 weeks per each month, regardless the month’s number of
+   * weeks. Weeks will be filled with the days from the next month.
+   */
+  fixedWeeks?: boolean;
+  /**
+   * Hide the row displaying the weekday row header.
+   *
+   * @since 9.0.0
+   */
+  hideWeekdayRow?: boolean;
+  /** Show the outside days (days falling in the next or the previous month). */
+  showOutsideDays?: boolean;
+  /**
+   * Show the week numbers column. Weeks are numbered according to the local
+   * week index.
+   *
+   * - To use ISO week numbering, use the `ISOWeek` prop.
+   * - To change how the week numbers are displayed, use the `formatters` prop.
+   */
+  showWeekNumber?: boolean;
+  /**
+   * Use ISO week dates instead of the locale setting. Setting this prop will
+   * ignore `weekStartsOn` and `firstWeekContainsDate`.
+   *
+   * @see https://en.wikipedia.org/wiki/ISO_week_date
+   */
+  ISOWeek?: boolean;
+  /** Change the components used for rendering the calendar elements. */
+  components?: CustomComponents;
+  /** Content to add to the grid as footer element. */
+  footer?: React.ReactNode;
+  /**
+   * When a selection mode is set, DayPicker will focus the first selected day
+   * (if set) or the today's date (if not disabled).
+   *
+   * Use this prop when you need to focus DayPicker after a user actions, for
+   * improved accessibility.
+   */
+  autoFocus?: boolean;
+  /** Apply the `disabled` modifier to the matching days. */
+  disabled?: Matcher | Matcher[] | undefined;
+  /**
+   * Apply the `hidden` modifier to the matching days. Will hide them from the
+   * calendar.
+   */
+  hidden?: Matcher | Matcher[] | undefined;
+  /**
+   * The today’s date. Default is the current date. This date will get the
+   * `today` modifier to style the day.
+   */
+  today?: Date;
+  /** Add modifiers to the matching days. */
+  modifiers?: Record<string, Matcher | Matcher[] | undefined> | undefined;
+  /**
+   * Labels creators to override the defaults. Use this prop to customize the
+   * aria-label attributes in DayPicker.
+   */
+  labels?: Partial<Labels>;
+  /**
+   * Formatters used to format dates to strings. Use this prop to override the
+   * default functions.
+   */
+  formatters?: Partial<Formatters>;
+  /**
+   * The text direction of the calendar. Use `ltr` for left-to-right (default)
+   * or `rtl` for right-to-left.
+   */
+  dir?: HTMLDivElement["dir"];
+  /**
+   * A cryptographic nonce ("number used once") which can be used by Content
+   * Security Policy for the inline `style` attributes.
+   */
+  nonce?: HTMLDivElement["nonce"];
+  /** Add a `title` attribute to the container element. */
+  title?: HTMLDivElement["title"];
+  /** Add the language tag to the container element. */
+  lang?: HTMLDivElement["lang"];
+  /**
+   * The date-fns locale object used to localize dates.
+   *
+   * @defaultValue en-US
+   * @see https://date-fns.org/docs/Locale
+   */
+  locale?: Locale | undefined;
+  /**
+   * The index of the first day of the week (0 - Sunday). Overrides the locale's
+   * one.
+   */
+  weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined;
+  /**
+   * The day of January, which is always in the first week of the year.
+   *
+   * @see https://date-fns.org/docs/getWeek
+   * @see https://en.wikipedia.org/wiki/Week#Numbering
+   */
+  firstWeekContainsDate?: 1 | 4;
+  /**
+   * Enable `DD` and `DDDD` for week year tokens when formatting or parsing
+   * dates.
+   *
+   * @see https://date-fns.org/docs/Unicode-Tokens
+   */
+  useAdditionalWeekYearTokens?: boolean | undefined;
+  /**
+   * Enable `YY` and `YYYY` for day of year tokens when formatting or parsing
+   * dates.
+   *
+   * @see https://date-fns.org/docs/Unicode-Tokens
+   */
+  useAdditionalDayOfYearTokens?: boolean | undefined;
+
+  /* EVENT HANDLERS */
+  /** Event fired when the user navigates between months. */
+  onMonthChange?: MonthChangeEventHandler;
+  /** Event handler when a day is clicked. */
+  onDayClick?: DayEventHandler<React.MouseEvent>;
+  /** Event handler when a day is focused. */
+  onDayFocus?: DayEventHandler<React.FocusEvent>;
+  /** Event handler when a day is blurred. */
+  onDayBlur?: DayEventHandler<React.FocusEvent>;
+  /** Event handler when the mouse enters a day. */
+  onDayMouseEnter?: DayEventHandler<React.MouseEvent>;
+  /** Event handler when the mouse leaves a day. */
+  onDayMouseLeave?: DayEventHandler<React.MouseEvent>;
+  /** Event handler when a key is pressed on a day. */
+  onDayKeyDown?: DayEventHandler<React.KeyboardEvent>;
+  /** Event handler when a key is released on a day. */
+  onDayKeyUp?: DayEventHandler<React.KeyboardEvent>;
+  /** Event handler when a key is pressed and released on a day. */
+  onDayKeyPress?: DayEventHandler<React.KeyboardEvent>;
+  /** Event handler when a pointer enters a day. */
+  onDayPointerEnter?: DayEventHandler<React.PointerEvent>;
+  /** Event handler when a pointer leaves a day. */
+  onDayPointerLeave?: DayEventHandler<React.PointerEvent>;
+  /** Event handler when a touch is cancelled on a day. */
+  onDayTouchCancel?: DayEventHandler<React.TouchEvent>;
+  /** Event handler when a touch ends on a day. */
+  onDayTouchEnd?: DayEventHandler<React.TouchEvent>;
+  /** Event handler when a touch moves on a day. */
+  onDayTouchMove?: DayEventHandler<React.TouchEvent>;
+  /** Event handler when a touch starts on a day. */
+  onDayTouchStart?: DayEventHandler<React.TouchEvent>;
+  /** Event handler when the next month button is clicked. */
+  onNextClick?: MonthChangeEventHandler;
+  /** Event handler when the previous month button is clicked. */
+  onPrevClick?: MonthChangeEventHandler;
+  /** Event handler when a week number is clicked */
+  onWeekNumberClick?: WeekNumberMouseEventHandler;
+}
+/**
+ * The props when the single selection is required.
+ *
+ * @group Props
+ */
+export interface PropsSingleRequired {
+  mode: "single";
+  required: true;
+  selected: Date;
+  onSelect?: (
+    selected: Date,
+    triggerDate: Date,
+    modifiers: Modifiers,
+    e: React.MouseEvent | React.KeyboardEvent
+  ) => void | undefined;
+}
+/**
+ * The props when the single selection is optional.
+ *
+ * @group Props
+ */
+export interface PropsSingle {
+  mode: "single";
+  required?: false | undefined;
+  selected?: Date | undefined;
+  onSelect?: (
+    selected: Date | undefined,
+    triggerDate: Date,
+    modifiers: Modifiers,
+    e: React.MouseEvent | React.KeyboardEvent
+  ) => void;
+}
+/**
+ * The props when the multiple selection is required.
+ *
+ * @group Props
+ */
+export interface PropsMultiRequired {
+  mode: "multiple";
+  required: true;
+  selected: Date[];
+  onSelect?: (
+    selected: Date[],
+    triggerDate: Date,
+    modifiers: Modifiers,
+    e: React.MouseEvent | React.KeyboardEvent
+  ) => void;
+  min?: number;
+  max?: number;
+}
+/**
+ * The props when the multiple selection is optional.
+ *
+ * @group Props
+ */
+export interface PropsMulti {
+  mode: "multiple";
+  required?: false | undefined;
+  selected?: Date[] | undefined;
+  onSelect?: (
+    selected: Date[] | undefined,
+    triggerDate: Date,
+    modifiers: Modifiers,
+    e: React.MouseEvent | React.KeyboardEvent
+  ) => void;
+  min?: number;
+  max?: number;
+}
+/**
+ * The props when the range selection is required.
+ *
+ * @group Props
+ */
+export interface PropsRangeRequired {
+  mode: "range";
+  required: true;
+  selected: DateRange;
+  onSelect?: (
+    selected: DateRange,
+    triggerDate: Date,
+    modifiers: Modifiers,
+    e: React.MouseEvent | React.KeyboardEvent
+  ) => void;
+  min?: number;
+  max?: number;
+}
+/**
+ * The props when the range selection is optional.
+ *
+ * @group Props
+ */
+export interface PropsRange {
+  mode: "range";
+  required?: false | undefined;
+  selected?: DateRange | undefined;
+  onSelect?: (
+    selected: DateRange | undefined,
+    triggerDate: Date,
+    modifiers: Modifiers,
+    e: React.MouseEvent | React.KeyboardEvent
+  ) => void | undefined;
+  min?: number;
+  max?: number;
+}
diff --git a/src/types/shared.ts b/src/types/shared.ts
new file mode 100644
index 000000000..c65716394
--- /dev/null
+++ b/src/types/shared.ts
@@ -0,0 +1,291 @@
+/* eslint-disable @typescript-eslint/ban-types */
+import type { MouseEvent, CSSProperties } from "react";
+
+import {
+  UI,
+  DayFlag,
+  CalendarFlag,
+  ChevronFlag,
+  WeekNumberFlag,
+  SelectionState
+} from "../UI";
+import * as components from "../components/custom-components";
+import {
+  formatCaption,
+  formatDay,
+  formatMonthCaption,
+  formatMonthDropdown,
+  formatWeekdayName,
+  formatWeekNumber,
+  formatYearCaption,
+  formatYearDropdown
+} from "../formatters";
+import {
+  labelDay,
+  labelCaption,
+  labelMonthDropdown,
+  labelNext,
+  labelPrevious,
+  labelWeekday,
+  labelWeekNumber,
+  labelWeekNumberHeader,
+  labelYearDropdown
+} from "../labels";
+
+/**
+ * Selection modes supported by DayPicker.
+ *
+ * - `single`: use DayPicker to select single days.
+ * - `multiple`: allow selecting multiple days.
+ * - `range`: use DayPicker to select a range of days.
+ *
+ * @see https://react-day-picker.js.org/next/using-daypicker/selection-modes
+ */
+export type Mode = "single" | "multiple" | "range";
+
+/**
+ * The components that can be changed using the `components` prop.
+ *
+ * @see https://github.com/gpbl/react-day-picker/blob/main/src/components/custom-components.ts
+ */
+export type CustomComponents = {
+  [key in keyof typeof components]?: (typeof components)[key];
+};
+
+/** Represent a map of formatters used to render localized content. */
+export type Formatters = {
+  /** Format the caption of a month grid. */
+  formatCaption: typeof formatCaption;
+  /** @deprecated Use {@link Formatters.formatCaption} instead. */
+  formatMonthCaption: typeof formatMonthCaption;
+  /** Format the label in the month dropdown. */
+  formatMonthDropdown: typeof formatMonthDropdown;
+  /** @deprecated Use {@link Formatters.formatYearDropdown} instead. */
+  formatYearCaption: typeof formatYearCaption;
+  /** Format the label in the year dropdown. */
+  formatYearDropdown: typeof formatYearDropdown;
+  /** Format the day in the day cell. */
+  formatDay: typeof formatDay;
+  /** Format the week number. */
+  formatWeekNumber: typeof formatWeekNumber;
+  /** Format the week day name in the header */
+  formatWeekdayName: typeof formatWeekdayName;
+};
+
+/** Map of functions to translate ARIA labels for the relative elements. */
+export type Labels = {
+  /** Return the label for the month dropdown. */
+  labelCaption: typeof labelCaption;
+  /** Return the label for the month dropdown. */
+  labelMonthDropdown: typeof labelMonthDropdown;
+  /** Return the label for the year dropdown. */
+  labelYearDropdown: typeof labelYearDropdown;
+  /** Return the label for the next month button. */
+  labelNext: typeof labelNext;
+  /** Return the label for the previous month button. */
+  labelPrevious: typeof labelPrevious;
+  /** Return the label for the day cell. */
+  labelDay: typeof labelDay;
+  /** Return the label for the weekday. */
+  labelWeekday: typeof labelWeekday;
+  /** Return the label for the week number. */
+  labelWeekNumber: typeof labelWeekNumber;
+  /**
+   * Return the label for the column of the week number.
+   *
+   * @since 9.0.0
+   */
+  labelWeekNumberHeader: typeof labelWeekNumberHeader;
+};
+
+/**
+ * A value or a function that matches a specific day.
+ *
+ * Matchers can be of different types:
+ *
+ * ```tsx
+ * // will always match the day
+ * const booleanMatcher: Matcher = true;
+ *
+ * // will match the today's date
+ * const dateMatcher: Matcher = new Date();
+ *
+ * // will match the days in the array
+ * const arrayMatcher: Matcher = [
+ *   new Date(2019, 1, 2),
+ *   new Date(2019, 1, 4)
+ * ];
+ *
+ * // will match days after the 2nd of February 2019
+ * const afterMatcher: DateAfter = { after: new Date(2019, 1, 2) };
+ * // will match days before the 2nd of February 2019 }
+ * const beforeMatcher: DateBefore = { before: new Date(2019, 1, 2) };
+ *
+ * // will match Sundays
+ * const dayOfWeekMatcher: DayOfWeek = {
+ *   dayOfWeek: 0
+ * };
+ *
+ * // will match the included days, except the two dates
+ * const intervalMatcher: DateInterval = {
+ *   after: new Date(2019, 1, 2),
+ *   before: new Date(2019, 1, 5)
+ * };
+ *
+ * // will match the included days, including the two dates
+ * const rangeMatcher: DateRange = {
+ *   from: new Date(2019, 1, 2),
+ *   to: new Date(2019, 1, 5)
+ * };
+ *
+ * // will match when the function return true
+ * const functionMatcher: Matcher = (day: Date) => {
+ *   return day.getMonth() === 2; // match when month is March
+ * };
+ * ```
+ */
+export type Matcher =
+  | boolean
+  | ((date: Date) => boolean)
+  | Date
+  | Date[]
+  | DateRange
+  | DateBefore
+  | DateAfter
+  | DateInterval
+  | DayOfWeek;
+
+/**
+ * A matcher to match a day falling after the specified date, with the date not
+ * included.
+ */
+export type DateAfter = { after: Date };
+
+/**
+ * A matcher to match a day falling before the specified date, with the date not
+ * included.
+ */
+export type DateBefore = { before: Date };
+
+/**
+ * A matcher to match a day falling before and/or after two dates, where the
+ * dates are not included.
+ */
+export type DateInterval = { before: Date; after: Date };
+
+/**
+ * A matcher to match a range of dates. The range can be open. Differently from
+ * {@link DateInterval}, the dates here are included.
+ */
+export type DateRange = { from: Date | undefined; to?: Date | undefined };
+
+/**
+ * A matcher to match a date being one of the specified days of the week (`0-6`,
+ * where `0` is Sunday).
+ */
+export type DayOfWeek = { dayOfWeek: number[] };
+
+/** A record with `data-*` attributes passed to `<DayPicker />`. */
+export type DataAttributes = Record<`data-${string}`, unknown>;
+
+/**
+ * The event handler triggered when interacting with a day.
+ *
+ * @template EventType - The event type that triggered the day event.
+ */
+export type DayEventHandler<EventType> = (
+  /** The date that has triggered the event. */
+  date: Date,
+  /** The modifiers belonging to the date. */
+  modifiers: Modifiers,
+  /** The DOM event that triggered this event. */
+  e: EventType
+) => void;
+
+/** The event handler when a month is changed in the calendar. */
+export type MonthChangeEventHandler = (month: Date) => void;
+
+/** The event handler when the week number is clicked. */
+export type WeekNumberMouseEventHandler = (
+  /** The week number that has been clicked. */
+  weekNumber: number,
+  /** The dates in the clicked week. */
+  dates: Date[],
+  /** The mouse event that triggered this event. */
+  e: MouseEvent
+) => void;
+
+/** Maps user interface elements, selection states, and flags to a CSS style. */
+export type Styles = {
+  [element in
+    | UI
+    | SelectionState
+    | DayFlag
+    | CalendarFlag
+    | ChevronFlag
+    | WeekNumberFlag]: CSSProperties | undefined;
+};
+
+/**
+ * Maps user interface elements, selection states, and flags to a CSS class
+ * name.
+ */
+export type ClassNames = {
+  [key in
+    | UI
+    | SelectionState
+    | DayFlag
+    | CalendarFlag
+    | ChevronFlag
+    | WeekNumberFlag]: string;
+};
+
+/** The flags that are matching a day in the calendar. */
+export type DayFlags = Record<DayFlag, boolean>;
+
+/** The selection state that are matching a day in the calendar. */
+export type SelectionStates = Record<SelectionState, boolean>;
+
+/** The modifiers that are matching a day in the calendar. */
+export type Modifiers = DayFlags & SelectionStates & CustomModifiers;
+
+/** The custom modifiers matching a day, passed to the `modifiers` prop. */
+export type CustomModifiers = Record<string, boolean>;
+
+/** The style to apply to each day element matching a modifier. */
+export type ModifiersStyles = Record<string, CSSProperties> &
+  Partial<Record<DayFlag, CSSProperties>>;
+
+/** The classnames to assign to each day element matching a modifier. */
+export type ModifiersClassNames = Record<string, string> &
+  Partial<Record<DayFlag & SelectionState, string>>;
+
+/**
+ * The props that have been deprecated since version 9.0.0.
+ *
+ * @since 9.0.0
+ * @see https://react-day-picker.js.org/next/upgrading
+ */
+export type V9DeprecatedProps =
+  /** Use `hidden` prop instead. */
+  | "fromDate"
+  /** Use `hidden` prop instead. */
+  | "toDate"
+  /** Use `startMonth` instead. */
+  | "fromMonth"
+  /** Use `endMonth` instead. */
+  | "toMonth"
+  /** Use `startMonth` instead. */
+  | "fromYear"
+  /** Use `endMonth` instead. */
+  | "toYear";
+
+export type MoveFocusDir = "after" | "before";
+
+export type MoveFocusBy =
+  | "day"
+  | "week"
+  | "startOfWeek"
+  | "endOfWeek"
+  | "month"
+  | "year";
diff --git a/src/utils/addToRange.ts b/src/utils/addToRange.ts
index 210737b05..b995d74b2 100644
--- a/src/utils/addToRange.ts
+++ b/src/utils/addToRange.ts
@@ -12,20 +12,17 @@ import type { DateRange } from "../types";
  *
  * @group Utilities
  */
-export function addToRange(
-  date: Date,
-  range?: DateRange
-): DateRange | undefined {
+export function addToRange(date: Date, range?: DateRange): DateRange {
   const { from, to } = range || {};
   if (from && to) {
     if (isSameDay(to, date) && isSameDay(from, date)) {
-      return undefined;
+      return { from: undefined, to: undefined };
     }
     if (isSameDay(to, date)) {
       return { from: to, to: undefined };
     }
     if (isSameDay(from, date)) {
-      return undefined;
+      return { from: undefined, to: undefined };
     }
     if (isAfter(from, date)) {
       return { from: date, to };
diff --git a/website/docs/advanced-guides/custom-components.mdx b/website/docs/advanced-guides/custom-components.mdx
index ab4ed2821..a63b2607b 100644
--- a/website/docs/advanced-guides/custom-components.mdx
+++ b/website/docs/advanced-guides/custom-components.mdx
@@ -70,12 +70,11 @@ export function MyDatePicker() {
 
 When creating custom components, you will find useful the [DayPicker hooks](../api/index.md#hooks). These utilities provide access to the internal state and methods of the DayPicker component.
 
-| Hooks                                              | Description                                                                |
-| :------------------------------------------------- | :------------------------------------------------------------------------- |
-| [`useCalendar`](../api/functions/useCalendar.md)   | Return the calendar state and navigation methods to navigate the calendar. |
-| [`useFocus`](../api/functions/useFocus.md)         | Share the focused day and the methods to move the focus.                   |
-| [`useProps`](../api/functions/useProps.md)         | Access to the props passed to DayPicker.                                   |
-| [`useSelection`](../api/functions/useSelection.md) | Access and change the currently selected values.                           |
+| Hooks                                                          | Description                                                                |
+| :------------------------------------------------------------- | :------------------------------------------------------------------------- |
+| [`useCalendarContext`](../api/functions/useCalendarContext.md) | Return the calendar state and navigation methods to navigate the calendar. |
+| [`useFocusContext`](../api/functions/useFocusContext.md)       | Share the focused day and the methods to move the focus.                   |
+| [`usePropsContext`](../api/functions/usePropsContext.md)       | Access to the props passed to DayPicker.                                   |
 
 ### Example: Range with Shift Key
 
@@ -88,11 +87,11 @@ import {
   DateRange,
   DayPicker,
   type DayProps,
-  useSelection
+  useRangeContext
 } from "react-day-picker";
 
 function DayWithShiftKey(props: DayProps) {
-  const { selected } = useSelection<"range">();
+  const { selected } = useRangeContext();
   const onClick = props.htmlAttributes?.onClick;
 
   const handleClick: MouseEventHandler<HTMLElement> = (e) => {
diff --git a/website/docs/advanced-guides/custom-modifiers.mdx b/website/docs/advanced-guides/custom-modifiers.mdx
index 15ffbf299..fe82fd377 100644
--- a/website/docs/advanced-guides/custom-modifiers.mdx
+++ b/website/docs/advanced-guides/custom-modifiers.mdx
@@ -23,7 +23,7 @@ In DayPicker, a **modifier** is added to a day when the day matches a specific c
 ### Understanding Modifiers
 
 - Use modifiers to change the appearance of the days in the calendar or to inspect the days the user has interacted with (e.g. picking a day)
-- DayPicker comes with some [pre-built modifiers](../api/type-aliases/InternalModifier.md), such as `disabled`, `selected`, `hidden`, `today`, `range_start`, etc. designed to cover the most common use cases.
+- DayPicker comes with some [pre-built modifiers](../api/type-aliases/Modifiers.md), such as `disabled`, `selected`, `hidden`, `today`, `range_start`, etc. designed to cover the most common use cases.
 - It is possible to implement custom modifiers, extending the behavior of DayPicker: see [Custom Modifiers](#custom-modifiers) below for more details.
 
 ## The `selected` Modifier
diff --git a/website/docs/upgrading.mdx b/website/docs/upgrading.mdx
index 9902363c5..9c292fee6 100644
--- a/website/docs/upgrading.mdx
+++ b/website/docs/upgrading.mdx
@@ -81,8 +81,8 @@ In case you are using DayPicker hooks in your custom components, you need to upd
 
 | Hook              | Upgrade Note                                                                                                            |
 | ----------------- | ----------------------------------------------------------------------------------------------------------------------- |
-| ~`useDayPicker`~  | Renamed to [`useProps`](./api/functions/useProps.md).                                                                   |
-| ~`useNavigation`~ | Renamed to [`useCalendar`](./api/functions/useCalendar.md).                                                             |
+| ~`useDayPicker`~  | Renamed to [`usePropsContext`](./api/functions/usePropsContext.md).                                                     |
+| ~`useNavigation`~ | Renamed to [`useCalendarContext`](./api/functions/useCalendarContext.md).                                               |
 | ~`useDayPicker`~  | Removed in favor of the `Day` custom component. See [custom components guide](./advanced-guides/custom-components.mdx). |
 
 ### 7. TypeScript: check for deprecated types
@@ -91,7 +91,7 @@ Many typings have been deprecated in favor of clarity and shorter names. If you
 
 ```diff
 - import type { DayPickerDefaultProps } from 'react-day-picker';
-+ import type { PropsDefault } from 'react-day-picker';
++ import type { PropsBase } from 'react-day-picker';
 ```
 
 See also the source of [types-deprecated.ts](https://github.com/gpbl/react-day-picker/blob/next/src/types-deprecated.ts).
@@ -104,15 +104,15 @@ See also the source of [types-deprecated.ts](https://github.com/gpbl/react-day-p
 | ~`DayPickerSingleProps`~       | This type has been renamed. Use [`PropsSingle`](./api/interfaces/PropsSingle.md) instead.                                   |
 | ~`DayPickerMultipleProps`~     | This type has been renamed. Use [`PropsMulti`](./api/interfaces/PropsMulti.md) instead.                                     |
 | ~`DayPickerRangeProps`~        | This type has been renamed. Use [`PropsRange`](./api/interfaces/PropsRange.md) instead.                                     |
-| ~`DayPickerDefaultProps`~      | This type has been renamed. Use [`PropsDefault`](./api/interfaces/PropsDefault.md) instead.                                 |
+| ~`DayPickerDefaultProps`~      | This type has been renamed. Use [`PropsBase`](./api/interfaces/PropsBase.md) instead.                                       |
 | ~`DaySelectionMode`~           | This type has been renamed. Use [`Mode`](./api/type-aliases/Mode.md) instead.                                               |
 | ~`Modifier`~                   | This type will be removed. Use `string` instead.                                                                            |
-| ~`SelectSingleEventHandler`~   | This type will be removed. Use [`SelectHandler<"single">`](./api/type-aliases/SelectHandler.md) instead.                    |
-| ~`SelectMultipleEventHandler`~ | This type will be removed. Use [`SelectHandler<"multiple">`](./api/type-aliases/SelectHandler.md) instead.                  |
-| ~`SelectRangeEventHandler`~    | This type will be removed. Use [`SelectHandler<"range">`](./api/type-aliases/SelectHandler.md) instead.                     |
+| ~`SelectSingleEventHandler`~   | This type will be removed. Use [`PropsSingle["onSelect]`](./api/interfaces/PropsSingle.md) instead.                         |
+| ~`SelectMultipleEventHandler`~ | This type will be removed. Use [`PropsMulti["onSelect]`](./api/interfaces/PropsMulti.md) instead.                           |
+| ~`SelectRangeEventHandler`~    | This type will be removed. Use [`PropsRange["onSelect]`](./api/interfaces/PropsRange.md) instead.                           |
 | ~`DayPickerProviderProps`~     | This type is not used anymore.                                                                                              |
-| ~`useDayPicker`~               | This type has been renamed to [`useProps`](./api/functions/useProps.md).                                                    |
-| ~`useNavigation`~              | This type has been renamed to [`useCalendar`](./api/functions/useCalendar.md).                                              |
+| ~`useDayPicker`~               | This type has been renamed to [`usePropsContext`](./api/functions/usePropsContext.md).                                      |
+| ~`useNavigation`~              | This type has been renamed to [`useCalendarContext`](./api/functions/useCalendarContext.md).                                |
 | ~`useDayRender`~               | This hook has been removed. To customize the rendering of a day, use the `htmlAttributes` prop in a custom `Day` component. |
 | ~`ContextProvidersProps`~      | This type is not used anymore.                                                                                              |
 | ~`DayLabel`~                   | Use `typeof labelDay` instead.                                                                                              |
diff --git a/website/docs/using-daypicker/selection-modes.mdx b/website/docs/using-daypicker/selection-modes.mdx
index b9b8573d4..07fe5a2f7 100644
--- a/website/docs/using-daypicker/selection-modes.mdx
+++ b/website/docs/using-daypicker/selection-modes.mdx
@@ -12,12 +12,12 @@ DayPicker provides predefined rules for day selection:
 
 The `mode` prop determines the selection mode. The `disabled` prop can be used to prevent the selection of specific days. The `selected` and `onSelect` props offer customization of the selection process.
 
-| Prop Name  | Type                                                                  | Description                                |
-| ---------- | --------------------------------------------------------------------- | ------------------------------------------ |
-| `mode`     | \| `"single"` <br/>\| `"multiple"` <br/>\| `"range"` <br/>\| `"none"` | Set a selection mode. Default is `"none"`. |
-| `disabled` | [`Matcher`](../api/type-aliases/Matcher.md) \| `Matcher[]`            | Disabled days that cannot be selected.     |
-| `selected` | [`Selected`](../api/type-aliases/Selected.md)                         | The selected day(s).                       |
-| `onSelect` | [`SelectHandler`](../api/type-aliases/SelectHandler.md)               | Event callback when a date is selected.    |
+| Prop Name  | Type                                                                               | Description                                |
+| ---------- | ---------------------------------------------------------------------------------- | ------------------------------------------ |
+| `mode`     | \| `"single"` <br/>\| `"multiple"` <br/>\| `"range"` <br/>\| `"none"`              | Set a selection mode. Default is `"none"`. |
+| `disabled` | [`Matcher`](../api/type-aliases/Matcher.md) \| `Matcher[]`                         | Disabled days that cannot be selected.     |
+| `selected` | `Date` \| `Date[]` \| [`DateRange`](../api/type-aliases/DateRange.md) \| undefined | The selected day(s).                       |
+| `onSelect` | `(selected, triggerDate, modifiers, e) => void`                                    | Event callback when a date is selected.    |
 
 ## Single Mode
 
@@ -33,11 +33,11 @@ When the `mode` prop is set to `"single"`, only one day can be selected.
 
 ### Single Mode Props
 
-| Prop Name  | Type                                                              | Description                             |
-| ---------- | ----------------------------------------------------------------- | --------------------------------------- |
-| `selected` | `Date \| undefined`                                               | The selected date.                      |
-| `onSelect` | [`SelectHandler<'single'>`](../api/type-aliases/SelectHandler.md) | Event callback when a date is selected. |
-| `required` | `boolean`                                                         | Make the selection required.            |
+| Prop Name  | Type                                            | Description                             |
+| ---------- | ----------------------------------------------- | --------------------------------------- |
+| `selected` | `Date \| undefined`                             | The selected date.                      |
+| `onSelect` | `(selected, triggerDate, modifiers, e) => void` | Event callback when a date is selected. |
+| `required` | `boolean`                                       | Make the selection required.            |
 
 The following code snippet will render a date picker with a single selected date. When a day is clicked, the `selectedDate` state is updated.
 
@@ -85,12 +85,12 @@ By setting the `mode` prop to `"multiple"`, DayPicker allows selecting multiple
 
 ### Multiple Mode Props
 
-| Prop Name  | Type                                                                | Description                             |
-| ---------- | ------------------------------------------------------------------- | --------------------------------------- |
-| `selected` | `Date[] \| undefined`                                               | The selected dates.                     |
-| `onSelect` | [`SelectHandler<'multiple'>`](../api/type-aliases/SelectHandler.md) | Event callback when a date is selected. |
-| `min`      | `number`                                                            | The minimum dates that can be selected. |
-| `max`      | `number`                                                            | The maximum dates that can be selected. |
+| Prop Name  | Type                                            | Description                             |
+| ---------- | ----------------------------------------------- | --------------------------------------- |
+| `selected` | `Date[] \| undefined`                           | The selected dates.                     |
+| `onSelect` | `(selected, triggerDate, modifiers, e) => void` | Event callback when a date is selected. |
+| `min`      | `number`                                        | The minimum dates that can be selected. |
+| `max`      | `number`                                        | The maximum dates that can be selected. |
 
 ### Min and Max Dates
 
@@ -125,12 +125,12 @@ When the `mode` prop is set to `"range"`, DayPicker allows selecting a continuou
 <DayPicker mode="range" />
 ```
 
-| Prop Name  | Type                                                             | Description                             |
-| ---------- | ---------------------------------------------------------------- | --------------------------------------- |
-| `selected` | [`DateRange`](../api/type-aliases/DateRange.md)                  | The selected range.                     |
-| `onSelect` | [`SelectHandler<'range'>`](../api/type-aliases/SelectHandler.md) | Event callback when a date is selected. |
-| `min`      | `number`                                                         | The minimum dates that can be selected. |
-| `max`      | `number`                                                         | The maximum dates that can be selected. |
+| Prop Name  | Type                                            | Description                             |
+| ---------- | ----------------------------------------------- | --------------------------------------- |
+| `selected` | [`DateRange`](../api/type-aliases/DateRange.md) | The selected range.                     |
+| `onSelect` | `(selected, triggerDate, modifiers, e) => void` | Event callback when a date is selected. |
+| `min`      | `number`                                        | The minimum dates that can be selected. |
+| `max`      | `number`                                        | The maximum dates that can be selected. |
 
 ```tsx
 import { useState } from "react";
diff --git a/website/docs/using-daypicker/styling.mdx b/website/docs/using-daypicker/styling.mdx
index 4f2d419ac..3633d026e 100644
--- a/website/docs/using-daypicker/styling.mdx
+++ b/website/docs/using-daypicker/styling.mdx
@@ -125,7 +125,9 @@ export function MyDatePicker() {
 
 ## Custom Class Names
 
-Use the `classNames` prop to use other classnames instead of the default ones. The [`ClassNames`](../api/type-aliases/ClassNames.md) type lists all the class names used by DayPicker. They are the value of the [`UI`](../api/enumerations/UI.md), [`DayModifiers`](../api/enumerations/DayModifier.md) and [`CalendarFlag`](../api/enumerations/CalendarFlag.md) enums.
+Use the `classNames` prop to use other classnames instead of the default ones. The [`ClassNames`](../api/type-aliases/ClassNames.md) type lists all the class names used by DayPicker.
+
+They are the value of the [`UI`](../api/enumerations/UI.md), [`DayFlag`](../api/enumerations/DayFlag.md), [`SelectionState`](../api/enumerations/SelectionState.md), and [`CalendarFlag`](../api/enumerations/CalendarFlag.md) enums.
 
 For example, to change the class name of the calendar container:
 
diff --git a/website/typedoc.mjs b/website/typedoc.mjs
index 40152b752..b45bf0660 100644
--- a/website/typedoc.mjs
+++ b/website/typedoc.mjs
@@ -17,11 +17,11 @@ const options = {
     "Props",
     "Classes",
     "Components",
-    "Hooks",
-    "Contexts",
-    "Utilities",
     "Formatters",
     "Labels",
+    "Utilities",
+    "Hooks",
+    "Contexts",
     "*"
   ],
   readme: "none",


Test patch:
diff --git a/examples/CustomWeek.test.tsx b/examples/CustomWeek.test.tsx
index fc83068db..9607aea0b 100644
--- a/examples/CustomWeek.test.tsx
+++ b/examples/CustomWeek.test.tsx
@@ -15,7 +15,7 @@ beforeEach(() => {
 
 describe("when a day is clicked", () => {
   beforeEach(async () => {
-    await act(() => user.click(gridcell(today)));
+    await user.click(gridcell(today));
   });
   test("the whole week should appear selected", () => {
     const week = [
@@ -33,7 +33,7 @@ describe("when a day is clicked", () => {
   });
   describe("when clicking the day again", () => {
     beforeEach(async () => {
-      await act(() => user.click(gridcell(today)));
+      await user.click(gridcell(today));
     });
     test("the whole week should not be selected", () => {
       const week = [
diff --git a/examples/Input.test.tsx b/examples/Input.test.tsx
index 15ad1ee3e..c199e765b 100644
--- a/examples/Input.test.tsx
+++ b/examples/Input.test.tsx
@@ -3,7 +3,7 @@ import React from "react";
 
 import { format } from "date-fns";
 
-import { act, render, screen } from "@/test/render";
+import { render, screen } from "@/test/render";
 import { user } from "@/test/user";
 
 import { Input } from "./Input";
diff --git a/examples/Range.test.tsx b/examples/Range.test.tsx
index 410d88b0a..feb12d07e 100644
--- a/examples/Range.test.tsx
+++ b/examples/Range.test.tsx
@@ -49,7 +49,7 @@ describe("when a day in the range is clicked", () => {
     describe("when a day in the range is clicked again", () => {
       const day = days[2];
       beforeEach(async () => act(() => user.click(gridcell(day))));
-      test("no day should be selected (??)", () => {
+      test("no day should be selected", () => {
         expect(getAllSelectedDays()).toHaveLength(0);
       });
       test("should match the snapshot", () => {
diff --git a/examples/Single.test.tsx b/examples/Single.test.tsx
index be15f1f9d..bcf8b16d2 100644
--- a/examples/Single.test.tsx
+++ b/examples/Single.test.tsx
@@ -21,13 +21,16 @@ describe("when a day is clicked", () => {
   });
   test("should appear as selected", () => {
     expect(gridcell(day)).toHaveAttribute("aria-selected", "true");
+    expect(gridcell(day)).toHaveFocus();
+    expect(gridcell(day)).toHaveClass("rdp-selected");
   });
   describe("when the day is clicked again", () => {
     beforeEach(async () => {
       await user.click(gridcell(day));
     });
-    test("should appear as not selected", () => {
+    test("should not appear as selected", () => {
       expect(gridcell(day)).not.toHaveAttribute("aria-selected");
+      expect(gridcell(day)).not.toHaveClass("rdp-selected");
     });
   });
 });
diff --git a/examples/TestCase2047.tsx b/examples/TestCase2047.tsx
index 8272302bf..24b1d94d0 100644
--- a/examples/TestCase2047.tsx
+++ b/examples/TestCase2047.tsx
@@ -10,7 +10,7 @@ export function TestCase2047() {
   const [selected, setSelected] = React.useState<Date>(defaultSelected);
   return (
     <DayPicker
-      defaultMonth={new Date(2024, 5, 10)}
+      defaultMonth={defaultMonth}
       mode="single"
       required
       selected={selected}
diff --git a/examples/Testcase1567.tsx b/examples/Testcase1567.tsx
index 95b686407..99eb79b13 100644
--- a/examples/Testcase1567.tsx
+++ b/examples/Testcase1567.tsx
@@ -1,6 +1,6 @@
 import React, { useState } from "react";
 
-import { DateRange, DayPicker, SelectHandler } from "react-day-picker";
+import { DateRange, DayPicker } from "react-day-picker";
 
 /**
  * Test case for issue #1567
@@ -13,9 +13,7 @@ export function TestCase1567() {
     to: new Date(2022, 9, 1)
   });
 
-  const handleChange: SelectHandler<"range"> = (
-    range: DateRange | undefined
-  ) => {
+  const handleChange = (range: DateRange | undefined) => {
     range && setSelected(range);
   };
   return (
diff --git a/examples/__snapshots__/Range.test.tsx.snap b/examples/__snapshots__/Range.test.tsx.snap
index f156b06fe..7e90ec07f 100644
--- a/examples/__snapshots__/Range.test.tsx.snap
+++ b/examples/__snapshots__/Range.test.tsx.snap
@@ -342,7 +342,7 @@ exports[`should match the snapshot 1`] = `
                 aria-colindex="1"
                 aria-label=""
                 aria-selected="true"
-                class="rdp-day rdp-focusable rdp-range_start rdp-selected"
+                class="rdp-day rdp-range_start rdp-selected rdp-focusable"
                 role="gridcell"
                 tabindex="0"
               >
@@ -356,7 +356,7 @@ exports[`should match the snapshot 1`] = `
                 aria-colindex="2"
                 aria-label=""
                 aria-selected="true"
-                class="rdp-day rdp-focusable rdp-range_middle rdp-selected"
+                class="rdp-day rdp-range_middle rdp-selected rdp-focusable"
                 role="gridcell"
                 tabindex="-1"
               >
@@ -370,7 +370,7 @@ exports[`should match the snapshot 1`] = `
                 aria-colindex="3"
                 aria-label=""
                 aria-selected="true"
-                class="rdp-day rdp-focusable rdp-range_middle rdp-selected"
+                class="rdp-day rdp-range_middle rdp-selected rdp-focusable"
                 role="gridcell"
                 tabindex="-1"
               >
@@ -384,7 +384,7 @@ exports[`should match the snapshot 1`] = `
                 aria-colindex="4"
                 aria-label=""
                 aria-selected="true"
-                class="rdp-day rdp-focusable rdp-range_middle rdp-selected"
+                class="rdp-day rdp-range_middle rdp-selected rdp-focusable"
                 role="gridcell"
                 tabindex="-1"
               >
@@ -398,7 +398,7 @@ exports[`should match the snapshot 1`] = `
                 aria-colindex="5"
                 aria-label=""
                 aria-selected="true"
-                class="rdp-day rdp-focusable rdp-range_end rdp-selected"
+                class="rdp-day rdp-range_end rdp-selected rdp-focusable"
                 role="gridcell"
                 tabindex="-1"
               >
diff --git a/src/contexts/calendar.test.tsx b/src/contexts/calendar.test.ts
similarity index 77%
rename from src/contexts/calendar.test.tsx
rename to src/contexts/calendar.test.ts
index cb5b5231d..4e74c748b 100644
--- a/src/contexts/calendar.test.tsx
+++ b/src/contexts/calendar.test.ts
@@ -1,18 +1,16 @@
-import React from "react";
-
 import { renderHook } from "@/test/renderHook";
 
-import { useCalendar } from "./calendar";
+import { useCalendarContext } from "./calendar";
 
 it("should return the next month", () => {
-  const { result } = renderHook(useCalendar, {
+  const { result } = renderHook(useCalendarContext, {
     month: new Date(2020, 0, 1)
   });
   expect(result.current.nextMonth).toEqual(new Date(2020, 1, 1));
 });
 
 it("should return the previous month", () => {
-  const { result } = renderHook(useCalendar, {
+  const { result } = renderHook(useCalendarContext, {
     month: new Date(2020, 0, 1)
   });
   expect(result.current.previousMonth).toEqual(new Date(2019, 11, 1));
@@ -20,14 +18,14 @@ it("should return the previous month", () => {
 
 describe("dropdown", () => {
   it("should return undefined if no fromMonth is provided", () => {
-    const { result } = renderHook(useCalendar, {
+    const { result } = renderHook(useCalendarContext, {
       fromMonth: undefined
     });
     expect(result.current.dropdownOptions.months).toBeUndefined();
   });
 
   it("should return undefined if no toMonth is provided", () => {
-    const { result } = renderHook(useCalendar, { toMonth: undefined });
+    const { result } = renderHook(useCalendarContext, { toMonth: undefined });
     expect(result.current.dropdownOptions.months).toBeUndefined();
   });
 
@@ -36,7 +34,7 @@ describe("dropdown", () => {
       fromMonth: new Date(2020, 1, 1),
       toMonth: new Date(2023, 2, 1)
     };
-    const { result } = renderHook(useCalendar, dayPicker);
+    const { result } = renderHook(useCalendarContext, dayPicker);
     const months = result.current.dropdownOptions.months;
     expect(months).toHaveLength(12);
     expect(months?.[0]).toEqual({
diff --git a/src/contexts/focus.test.tsx b/src/contexts/focus.test.tsx
index dcf908486..27b477173 100644
--- a/src/contexts/focus.test.tsx
+++ b/src/contexts/focus.test.tsx
@@ -1,8 +1,6 @@
-import { gridcell } from "@/test/elements";
 import { renderHook } from "@/test/renderHook";
-import { user } from "@/test/user";
 
-import { useFocus } from "./focus";
+import { useFocusContext } from "./focus";
 
 const month = new Date(2020, 0, 1);
 const today = new Date(2020, 0, 14);
@@ -10,13 +8,13 @@ const today = new Date(2020, 0, 14);
 describe("autoFocusTarget", () => {
   describe("when not in interactive", () => {
     test("the auto focus target is undefined", () => {
-      const { result } = renderHook(useFocus, { month, today });
+      const { result } = renderHook(useFocusContext, { month, today });
       expect(result.current.autoFocusTarget).toBeUndefined();
     });
   });
   describe("when in selection mode", () => {
     test("the autofocus target should be today", () => {
-      const { result } = renderHook(useFocus, {
+      const { result } = renderHook(useFocusContext, {
         month,
         today,
         mode: "single"
@@ -27,10 +25,10 @@ describe("autoFocusTarget", () => {
     });
     describe("if today is disabled", () => {
       test("the autofocus target should be the first focusable day (the 1st of month)", () => {
-        const { result } = renderHook(useFocus, {
+        const { result } = renderHook(useFocusContext, {
           month,
           today,
-          mode: "multiple",
+          mode: "single",
           disabled: [today]
         });
         expect(result.current.autoFocusTarget?.date).toEqual(month);
diff --git a/src/formatters/formatMonthDropdown.test.ts b/src/formatters/formatMonthDropdown.test.ts
index 19651e680..58133b617 100644
--- a/src/formatters/formatMonthDropdown.test.ts
+++ b/src/formatters/formatMonthDropdown.test.ts
@@ -1,4 +1,3 @@
-import { Month } from "date-fns";
 import { es } from "date-fns/locale/es";
 
 import { formatMonthDropdown } from "./formatMonthDropdown";
@@ -6,13 +5,11 @@ import { formatMonthDropdown } from "./formatMonthDropdown";
 const date = new Date(2022, 10, 21);
 
 test("should return the formatted month dropdown label", () => {
-  expect(formatMonthDropdown(date.getMonth() as Month)).toEqual("November");
+  expect(formatMonthDropdown(date.getMonth())).toEqual("November");
 });
 
 describe("when a locale is passed in", () => {
   test("should format using the locale", () => {
-    expect(formatMonthDropdown(date.getMonth() as Month, es)).toEqual(
-      "noviembre"
-    );
+    expect(formatMonthDropdown(date.getMonth(), es)).toEqual("noviembre");
   });
 });
diff --git a/src/helpers/calculateMonthWeeks.test.ts b/src/helpers/calculateMonthWeeks.test.ts
deleted file mode 100644
index fc3ca7618..000000000
--- a/src/helpers/calculateMonthWeeks.test.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import { enUS } from "date-fns/locale";
-
-import { calculateMonthWeeks } from "./calculateMonthWeeks";
-
-test("calculate weekly ranges correctly without options", () => {
-  const startDate = new Date(2023, 0, 1); // January 1, 2023
-  const endDate = new Date(2023, 0, 15); // January 15, 2023
-
-  const result = calculateMonthWeeks(startDate, endDate);
-
-  expect(result).toBeInstanceOf(Array);
-  expect(result).toHaveLength(3);
-
-  expect(result[0].dates).toHaveLength(7);
-  expect(result[0].dates[0]).toBeSunday();
-
-  expect(result[1].dates).toHaveLength(7);
-  expect(result[2].dates).toHaveLength(7);
-  expect(result[2].dates[6]).toHaveDate(21);
-  expect(result[2].dates[6]).toBeSaturday();
-});
-
-test("calculate weekly ranges correctly with ISOWeek option", () => {
-  const startDate = new Date(2023, 0, 1);
-  const endDate = new Date(2023, 0, 17); // January 16, 2023
-  const options = { ISOWeek: true };
-
-  const result = calculateMonthWeeks(startDate, endDate, options);
-
-  expect(result[0].dates).toHaveLength(7);
-  expect(result[0].dates[0]).toBeMonday();
-  expect(result[2].dates[6]).toBeSunday();
-  expect(result[2].dates[6]).toHaveDate(15);
-  expect(result[3].dates[6]).toHaveDate(22);
-});
-
-test("respect locale and weekStartsOn options", () => {
-  const startDate = new Date(2023, 0, 1);
-  const endDate = new Date(2023, 0, 15);
-  const options = { locale: enUS, weekStartsOn: 2 as 0 | 2 }; // start on Tuesday
-
-  const result = calculateMonthWeeks(startDate, endDate, options);
-
-  expect(result[0].dates[0]).toBeTuesday();
-  expect(result[1].dates[0]).toBeTuesday();
-  expect(result[2].dates[0]).toBeTuesday();
-});
diff --git a/src/helpers/getDisplayMonths.test.ts b/src/helpers/getDisplayMonths.test.ts
index 752ba3087..5372ea2d6 100644
--- a/src/helpers/getDisplayMonths.test.ts
+++ b/src/helpers/getDisplayMonths.test.ts
@@ -1,34 +1,36 @@
 import { getDisplayMonths } from "./getDisplayMonths";
 
-describe("when the number of months is 1", () => {
+describe("getDisplayMonths", () => {
   it("should return the months to display in the calendar", () => {
     const firstMonth = new Date(2020, 0);
     const expectedResult = [new Date(2020, 0)];
-    const result = getDisplayMonths(firstMonth, { numberOfMonths: 1 });
+    const result = getDisplayMonths(firstMonth, {
+      numberOfMonths: 1,
+      endMonth: undefined
+    });
     expect(result).toEqual(expectedResult);
   });
-});
 
-describe("when the number of months is greater than 1", () => {
-  it("should return the months to display in the calendar", () => {
+  it("should return the months to display in the calendar when the number of months is greater than 1", () => {
     const firstMonth = new Date(2020, 0);
     const expectedResult = [
       new Date(2020, 0),
       new Date(2020, 1),
       new Date(2020, 2)
     ];
-    const result = getDisplayMonths(firstMonth, { numberOfMonths: 3 });
+    const result = getDisplayMonths(firstMonth, {
+      numberOfMonths: 3,
+      endMonth: undefined
+    });
     expect(result).toEqual(expectedResult);
   });
-});
 
-describe("when passing a max date", () => {
-  it("should return the months to display in the calendar", () => {
+  it("should return the months to display in the calendar when passing a max date", () => {
     const firstMonth = new Date(2020, 0);
     const expectedResult = [new Date(2020, 0), new Date(2020, 1)];
     const result = getDisplayMonths(firstMonth, {
       numberOfMonths: 3,
-      toDate: new Date(2020, 1, 10)
+      endMonth: new Date(2020, 1, 10)
     });
     expect(result).toEqual(expectedResult);
   });
diff --git a/src/helpers/getDropdownMonths.test.ts b/src/helpers/getDropdownMonths.test.ts
index aa0a1a1f1..b670ddf0b 100644
--- a/src/helpers/getDropdownMonths.test.ts
+++ b/src/helpers/getDropdownMonths.test.ts
@@ -1,69 +1,36 @@
-import * as formatters from "../formatters";
+import { type Locale, format } from "date-fns";
+import { enUS as locale } from "date-fns/locale";
 
 import { getDropdownMonths } from "./getDropdownMonths";
-
-test("returns undefined if `startMonth` is not defined", () => {
-  const result = getDropdownMonths({
-    startMonth: undefined,
-    endMonth: new Date(),
-    formatters
+import { getFormatters } from "./getFormatters";
+
+test("return correct dropdown options", () => {
+  const displayMonth = new Date(2022, 0, 1); // January 2022
+  const startMonth = new Date(2022, 0, 1); // January 2022
+  const endMonth = new Date(2022, 11, 31); // December 2022
+  const formatters = getFormatters({
+    formatMonthDropdown: (month: number, locale?: Locale) =>
+      format(new Date(2022, month), "MMMM", { locale })
   });
-  expect(result).toBeUndefined();
-});
-
-test("returns undefined if `endMonth` is not defined", () => {
-  const result = getDropdownMonths({
-    startMonth: new Date(),
-    endMonth: undefined,
-    formatters
+  const result = getDropdownMonths(displayMonth, {
+    formatters,
+    locale,
+    startMonth,
+    endMonth
   });
-  expect(result).toBeUndefined();
-});
-
-test("returns sorted months between `startMonth` and `endMonth`", () => {
-  const startMonth = new Date(2023, 0, 1);
-  const endMonth = new Date(2023, 11, 31);
-
-  const result = getDropdownMonths(
-    { startMonth, endMonth, formatters },
-    startMonth.getFullYear()
-  );
 
-  expect(result).toBeDefined();
-  expect(result).toHaveLength(12);
-  if (!result) throw new Error("Unexpected undefined result");
-  for (let i = 0; i < result.length - 1; i++) {
-    expect(result[i].value).toBeLessThan(result[i + 1].value);
-  }
-});
-
-test("formats month labels correctly", () => {
-  const startMonth = new Date(2023, 3, 1);
-  const endMonth = new Date(2023, 11, 31);
-  const result = getDropdownMonths(
-    { startMonth, endMonth, formatters },
-    startMonth.getFullYear()
-  );
-  if (!result) throw new Error("Unexpected undefined result");
-  expect(result[0]).toEqual({ disabled: false, label: "April", value: 3 });
-  expect(result[8]).toEqual({ disabled: false, label: "December", value: 11 });
-});
-
-describe("when using a custom formatter", () => {
-  test("formats month labels correctly", () => {
-    const startMonth = new Date(2023, 0, 1);
-    const endMonth = new Date(2023, 11, 31);
-    const result = getDropdownMonths(
-      {
-        startMonth,
-        endMonth,
-        formatters: {
-          formatMonthDropdown: (month) => `Month ${month.toString()}`
-        }
-      },
-      startMonth.getFullYear()
-    );
-    if (!result) throw new Error("Unexpected undefined result");
-    expect(result[0]).toEqual({ disabled: false, label: "Month 0", value: 0 });
-  });
+  expect(result).toEqual([
+    { value: 0, label: "January", disabled: false },
+    { value: 1, label: "February", disabled: false },
+    { value: 2, label: "March", disabled: false },
+    { value: 3, label: "April", disabled: false },
+    { value: 4, label: "May", disabled: false },
+    { value: 5, label: "June", disabled: false },
+    { value: 6, label: "July", disabled: false },
+    { value: 7, label: "August", disabled: false },
+    { value: 8, label: "September", disabled: false },
+    { value: 9, label: "October", disabled: false },
+    { value: 10, label: "November", disabled: false },
+    { value: 11, label: "December", disabled: false }
+  ]);
 });
diff --git a/src/helpers/getDropdownYears.test.ts b/src/helpers/getDropdownYears.test.ts
index 459d6a2dd..0c863e877 100644
--- a/src/helpers/getDropdownYears.test.ts
+++ b/src/helpers/getDropdownYears.test.ts
@@ -1,72 +1,48 @@
-import { formatYearDropdown } from "../formatters";
+import { enUS as locale } from "date-fns/locale";
 
 import { getDropdownYears } from "./getDropdownYears";
+import { getFormatters } from "./getFormatters";
 
-it("returns undefined if startMonth is not defined", () => {
-  const result = getDropdownYears({
-    endMonth: new Date(),
+test("return undefined if startMonth or endMonth is not provided", () => {
+  const displayMonth = new Date(2022, 0, 1); // January 2022
+  const formatters = getFormatters({
+    formatYearDropdown: (year: number) => `${year}`
+  });
+  const result1 = getDropdownYears(displayMonth, {
+    formatters,
+    locale,
     startMonth: undefined,
-    formatters: { formatYearDropdown }
+    endMonth: new Date(2022, 11, 31)
   });
-  expect(result).toBeUndefined();
-});
-
-it("returns undefined if `endMonth` is not defined", () => {
-  const result = getDropdownYears({
-    startMonth: new Date(),
-    endMonth: undefined,
-    formatters: { formatYearDropdown }
+  const result2 = getDropdownYears(displayMonth, {
+    formatters,
+    locale,
+    startMonth: new Date(2022, 0, 1),
+    endMonth: undefined
   });
-  expect(result).toBeUndefined();
+
+  expect(result1).toBeUndefined();
+  expect(result2).toBeUndefined();
 });
 
-it("returns an array of years between `startMonth` and `endMonth`", () => {
-  const startMonth = new Date(2020, 0, 1);
-  const endMonth = new Date(2022, 11, 31);
-  const result = getDropdownYears({
-    startMonth,
-    endMonth,
-    formatters: {
-      formatYearDropdown: (year: number): string =>
-        `Formatted ${year.toString()}`
-    }
+test("return correct dropdown options", () => {
+  const displayMonth = new Date(2022, 0, 1); // January 2022
+  const startMonth = new Date(2022, 0, 1); // January 2022
+  const endMonth = new Date(2024, 11, 31); // December 2024
+  const formatters = getFormatters({
+    formatYearDropdown: (year: number) => `${year}`
   });
 
-  expect(result).toEqual([
-    {
-      disabled: false,
-      label: "Formatted 2020",
-      value: 2020
-    },
-    {
-      disabled: false,
-      label: "Formatted 2021",
-      value: 2021
-    },
-    {
-      disabled: false,
-      label: "Formatted 2022",
-      value: 2022
-    }
-  ]);
-});
-
-it("handles same year for startMonth and endMonth", () => {
-  const year = new Date(2021, 5, 15);
-  const result = getDropdownYears({
-    startMonth: year,
-    endMonth: year,
-    formatters: {
-      formatYearDropdown: (year: number): string =>
-        `Formatted ${year.toString()}`
-    }
+  const result = getDropdownYears(displayMonth, {
+    formatters,
+    locale,
+    startMonth,
+    endMonth
   });
 
   expect(result).toEqual([
-    {
-      disabled: false,
-      label: "Formatted 2021",
-      value: 2021
-    }
+    { value: 2022, label: "2022", disabled: false },
+    { value: 2023, label: "2023", disabled: false },
+    { value: 2024, label: "2024", disabled: false }
   ]);
 });
diff --git a/src/helpers/getFormatters.test.ts b/src/helpers/getFormatters.test.ts
index 94d136e2c..ceafc7e42 100644
--- a/src/helpers/getFormatters.test.ts
+++ b/src/helpers/getFormatters.test.ts
@@ -1,5 +1,4 @@
 import * as defaultFormatters from "../formatters";
-import type { PropsBase } from "../types";
 
 import { getFormatters } from "./getFormatters";
 
diff --git a/src/helpers/getStartMonth.test.ts b/src/helpers/getInitialMonth.test.ts
similarity index 83%
rename from src/helpers/getStartMonth.test.ts
rename to src/helpers/getInitialMonth.test.ts
index 8eb6a591a..9f3a351da 100644
--- a/src/helpers/getStartMonth.test.ts
+++ b/src/helpers/getInitialMonth.test.ts
@@ -1,26 +1,26 @@
 import { addMonths, isSameMonth } from "date-fns";
 
-import { getStartMonth } from "./getStartMonth";
+import { getInitialMonth } from "./getInitialMonth";
 
 describe("when no endMonth is given", () => {
   describe("when month is in context", () => {
     const month = new Date(2010, 11, 12);
     it("return that month", () => {
-      const startMonth = getStartMonth({ month });
+      const startMonth = getInitialMonth({ month });
       expect(isSameMonth(startMonth, month)).toBe(true);
     });
   });
   describe("when defaultMonth is in context", () => {
     const defaultMonth = new Date(2010, 11, 12);
     it("return that month", () => {
-      const startMonth = getStartMonth({ defaultMonth });
+      const startMonth = getInitialMonth({ defaultMonth });
       expect(isSameMonth(startMonth, defaultMonth)).toBe(true);
     });
   });
   describe("when no month or defaultMonth are in context", () => {
     const today = new Date(2010, 11, 12);
     it("return the today month", () => {
-      const startMonth = getStartMonth({ today });
+      const startMonth = getInitialMonth({ today });
       expect(isSameMonth(startMonth, today)).toBe(true);
     });
   });
@@ -32,7 +32,7 @@ describe("when endMonth is given", () => {
     describe("when the number of month is 1", () => {
       const numberOfMonths = 1;
       it("return the endMonth", () => {
-        const startMonth = getStartMonth({
+        const startMonth = getInitialMonth({
           month,
           endMonth,
           numberOfMonths
@@ -43,7 +43,7 @@ describe("when endMonth is given", () => {
     describe("when the number of month is 3", () => {
       const numberOfMonths = 3;
       it("return the endMonth plus the number of months", () => {
-        const startMonth = getStartMonth({
+        const startMonth = getInitialMonth({
           month,
           endMonth,
           numberOfMonths
diff --git a/src/helpers/getMonthWeeks.test.ts b/src/helpers/getMonthWeeks.test.ts
deleted file mode 100644
index 6d9e6e3aa..000000000
--- a/src/helpers/getMonthWeeks.test.ts
+++ /dev/null
@@ -1,100 +0,0 @@
-import { enGB, enUS } from "date-fns/locale";
-
-import { getMonthWeeks } from "./getMonthWeeks";
-
-describe('when using the "enUS" locale', () => {
-  const locale = enUS;
-  describe("when using fixed weeks", () => {
-    const useFixedWeeks = true;
-    describe("when getting the weeks for December 2022", () => {
-      const date = new Date(2022, 11);
-      const weeks = getMonthWeeks(date, { useFixedWeeks, locale });
-      test("should return 49 - 1 week numbers", () => {
-        const weekNumbers = weeks.map((week) => week.weekNumber);
-        const expectedResult = [49, 50, 51, 52, 53, 1];
-        expect(weekNumbers).toEqual(expectedResult);
-      });
-      test("the last week should be the one in the next year", () => {
-        const lastWeek = weeks[weeks.length - 1];
-        const lastWeekDates = lastWeek.dates.map((date) => date.getDate());
-        const expectedResult = [1, 2, 3, 4, 5, 6, 7];
-        expect(lastWeekDates).toEqual(expectedResult);
-      });
-    });
-    describe("when getting the weeks for December 2021", () => {
-      const weeks = getMonthWeeks(new Date(2021, 11), {
-        useFixedWeeks: false,
-        locale: enUS
-      });
-      test("should return 49 - 1 week numbers", () => {
-        const weekNumbers = weeks.map((week) => week.weekNumber);
-        const expectedResult = [49, 50, 51, 52, 1];
-        expect(weekNumbers).toEqual(expectedResult);
-      });
-      test("the last week should be the last in the year", () => {
-        const lastWeek = weeks[weeks.length - 1];
-        const lastWeekDates = lastWeek.dates.map((date) => date.getDate());
-        const expectedResult = [26, 27, 28, 29, 30, 31, 1];
-        expect(lastWeekDates).toEqual(expectedResult);
-      });
-      test("week 1 contains the first day of the new year", () => {
-        expect(weeks[4].dates.map((date) => date.getDate())).toEqual([
-          26, 27, 28, 29, 30, 31, 1
-        ]);
-      });
-    });
-  });
-});
-
-describe('when using the "enGB" locale', () => {
-  const locale = enGB;
-  describe("when getting the weeks for January 2022", () => {
-    const date = new Date(2022, 0);
-    const weeks = getMonthWeeks(date, { locale });
-    test("the first week should be the last of the previous year", () => {
-      const weekNumbers = weeks.map((week) => week.weekNumber);
-      expect(weekNumbers[0]).toEqual(52);
-    });
-    test("the first week should contain days from previous year", () => {
-      expect(weeks[0].dates.map((date) => date.getDate())).toEqual([
-        27, 28, 29, 30, 31, 1, 2
-      ]);
-    });
-    test("the last week should be the last of January", () => {
-      const weekNumbers = weeks.map((week) => week.weekNumber);
-      expect(weekNumbers[weekNumbers.length - 1]).toEqual(5);
-    });
-  });
-  describe("when setting thursday as first day of year", () => {
-    const date = new Date(2022, 0);
-    const weeks = getMonthWeeks(date, { locale, firstWeekContainsDate: 4 });
-    test("the number of week should have number 52", () => {
-      const weekNumbers = weeks.map((week) => week.weekNumber);
-      expect(weekNumbers[0]).toEqual(52);
-    });
-  });
-});
-
-describe("when using the ISOWeek numbers", () => {
-  const locale = enUS;
-  describe("when getting the weeks for September 2022", () => {
-    const date = new Date(2022, 8);
-    const weeks = getMonthWeeks(date, { locale, ISOWeek: true });
-    test("the last week should have number 39", () => {
-      const weekNumbers = weeks.map((week) => week.weekNumber);
-      expect(weekNumbers[weekNumbers.length - 1]).toEqual(39);
-    });
-  });
-});
-
-describe("when not using the ISOWeek numbers", () => {
-  const locale = enUS;
-  describe("when getting the weeks for September 2022", () => {
-    const date = new Date(2022, 8);
-    const weeks = getMonthWeeks(date, { locale, ISOWeek: false });
-    test("the last week should have number 40", () => {
-      const weekNumbers = weeks.map((week) => week.weekNumber);
-      expect(weekNumbers[weekNumbers.length - 1]).toEqual(40);
-    });
-  });
-});
diff --git a/src/helpers/getMonths.test.ts b/src/helpers/getMonths.test.ts
index 29b7c72b5..192919adf 100644
--- a/src/helpers/getMonths.test.ts
+++ b/src/helpers/getMonths.test.ts
@@ -1,96 +1,90 @@
 import { CalendarMonth } from "../classes";
+import type { PropsContextValue } from "../contexts/props";
 
-import { getDates } from "./getDates";
 import { getMonths } from "./getMonths";
 
-describe("when first and last months are the same", () => {
-  it("should return the months to display in the calendar", () => {
-    const month = new Date(2024, 0, 1);
-    const dates = getDates([month]);
-    const months = getMonths([month], dates);
-    expect(months).toHaveLength(1);
-    expect(months[0]).toBeInstanceOf(CalendarMonth);
-    expect(months[0].date).toBe(month);
-    expect(months[0].weeks[0].days[1].date).toStrictEqual(new Date(2024, 0, 1));
-    expect(months[months.length - 1]).toBeInstanceOf(CalendarMonth);
-    expect(months[0].weeks[4].days[1].date).toStrictEqual(
-      new Date(2024, 0, 29)
-    );
-  });
-  describe("when week starts on Saturday", () => {
-    it("should return the months to display in the calendar", () => {
-      const month = new Date(2024, 0, 1);
-      const dates = getDates([month], undefined, { weekStartsOn: 6 });
-      const months = getMonths([month], dates, { weekStartsOn: 6 });
-
-      expect(months[0].weeks[0].days[0].date).toStrictEqual(
-        new Date(2023, 11, 30)
-      );
-      expect(months[months.length - 1]).toBeInstanceOf(CalendarMonth);
-      expect(months[0].weeks[4].days[0].date).toStrictEqual(
-        new Date(2024, 0, 27)
-      );
-    });
-  });
-  describe("when using ISO weeks", () => {
-    it("should return the months to display in the calendar", () => {
-      const month = new Date(2024, 0, 1);
-      const dates = getDates([month], undefined, { ISOWeek: true });
-      const months = getMonths([month], dates);
-
-      expect(months[0].weeks[0].days[1].date).toStrictEqual(
-        new Date(2024, 0, 2)
-      );
-      expect(months[months.length - 1]).toBeInstanceOf(CalendarMonth);
-      expect(months[0].weeks[4].days[1].date).toStrictEqual(
-        new Date(2024, 0, 29)
-      );
-    });
-  });
-  describe("when using fixed weeks", () => {
-    it("should return the months to display in the calendar", () => {
-      const month = new Date(2023, 4, 1); // month with 4 weeks
-      const dates = getDates([month], undefined, { fixedWeeks: true });
-      const months = getMonths([month], dates);
-
-      expect(months[0].weeks[0].days[0].date).toStrictEqual(
-        new Date(2023, 3, 30)
-      );
-      expect(months[months.length - 1]).toBeInstanceOf(CalendarMonth);
-      expect(months[0].weeks[4].days[1].date).toStrictEqual(
-        new Date(2023, 4, 29)
-      );
-    });
-  });
+const mockDates = [
+  new Date(2023, 4, 27), // May 1, 2023
+  new Date(2023, 5, 1), // June 1, 2023
+  new Date(2023, 5, 8), // June 2, 2023
+  new Date(2023, 5, 15), // June 15, 2023
+  new Date(2023, 5, 22), // June 22, 2023
+  new Date(2023, 5, 30), // June 30, 2023
+  new Date(2023, 6, 6) // June 30, 2023
+];
+
+const mockProps: Pick<
+  PropsContextValue,
+  | "fixedWeeks"
+  | "ISOWeek"
+  | "locale"
+  | "weekStartsOn"
+  | "reverseMonths"
+  | "firstWeekContainsDate"
+> = {
+  fixedWeeks: false,
+  ISOWeek: false,
+  locale: undefined,
+  weekStartsOn: 0, // Sunday
+  reverseMonths: false,
+  firstWeekContainsDate: 1
+};
+
+it("should return the correct months without ISO weeks and reverse months", () => {
+  const displayMonths = [new Date(2023, 5, 1)]; // June 2023
+
+  const result = getMonths(displayMonths, mockDates, mockProps);
+
+  expect(result).toHaveLength(1);
+  expect(result[0]).toBeInstanceOf(CalendarMonth);
+  expect(result[0].weeks).toHaveLength(5); // June 2023 has 5 weeks
 });
 
-describe("when first and last months are not the same", () => {
-  it("should return the months to display in the calendar", () => {
-    const firstMonth = new Date(2024, 0, 1);
-    const lastMonth = new Date(2024, 2, 1);
-    const dates = getDates([firstMonth, lastMonth]);
-    const months = getMonths([firstMonth, lastMonth], dates);
-    expect(months).toHaveLength(2);
-    expect(months[0]).toBeInstanceOf(CalendarMonth);
-    expect(months[0].date).toBe(firstMonth);
-    expect(months[0].weeks[0].days[1].date).toStrictEqual(new Date(2024, 0, 1));
-    expect(months[months.length - 1]).toBeInstanceOf(CalendarMonth);
-    expect(months[0].weeks[4].days[1].date).toStrictEqual(
-      new Date(2024, 0, 29)
-    );
-  });
+it("should handle ISO weeks", () => {
+  const displayMonths = [new Date(2023, 5, 1)]; // June 2023
+
+  const isoProps = { ...mockProps, ISOWeek: true };
+
+  const result = getMonths(displayMonths, mockDates, isoProps);
+
+  expect(result).toHaveLength(1);
+  expect(result[0]).toBeInstanceOf(CalendarMonth);
+  expect(result[0].weeks).toHaveLength(5); // June 2023 has 5 ISO weeks
 });
 
-describe("when reversing the order of the months", () => {
-  it("should return the months to display in the calendar", () => {
-    const firstMonth = new Date(2024, 0, 1);
-    const lastMonth = new Date(2024, 2, 1);
-    const dates = getDates([firstMonth, lastMonth]);
-    const months = getMonths([firstMonth, lastMonth], dates, {
-      reverseMonths: true
-    });
-    expect(months).toHaveLength(2);
-    expect(months[0].date).toBe(lastMonth);
-    expect(months[months.length - 1].date).toBe(firstMonth);
-  });
+it("should handle reverse months", () => {
+  const displayMonths = [
+    new Date(2023, 4, 1), // May 2023
+    new Date(2023, 5, 1) // June 2023
+  ];
+
+  const reverseProps = { ...mockProps, reverseMonths: true };
+
+  const result = getMonths(displayMonths, mockDates, reverseProps);
+
+  expect(result).toHaveLength(2);
+  expect(result[0].date).toEqual(new Date(2023, 5, 1)); // June 2023
+  expect(result[1].date).toEqual(new Date(2023, 4, 1)); // May 2023
+});
+
+it("should handle fixed weeks", () => {
+  const displayMonths = [new Date(2023, 5, 1)]; // June 2023
+
+  const fixedWeeksProps = { ...mockProps, fixedWeeks: true };
+
+  const result = getMonths(displayMonths, mockDates, fixedWeeksProps);
+
+  expect(result).toHaveLength(1);
+  expect(result[0]).toBeInstanceOf(CalendarMonth);
+  expect(result[0].weeks).toHaveLength(6); // Fixed weeks should ensure 6 weeks in the month view
+});
+
+it("should handle months with no dates", () => {
+  const displayMonths = [new Date(2023, 5, 1)]; // June 2023
+
+  const result = getMonths(displayMonths, [], mockProps);
+
+  expect(result).toHaveLength(1);
+  expect(result[0]).toBeInstanceOf(CalendarMonth);
+  expect(result[0].weeks).toHaveLength(0); // No dates should result in no weeks
 });
diff --git a/src/helpers/getNextFocus.test.tsx b/src/helpers/getNextFocus.test.tsx
index 4207d8d22..c0baac850 100644
--- a/src/helpers/getNextFocus.test.tsx
+++ b/src/helpers/getNextFocus.test.tsx
@@ -1,14 +1,13 @@
 import React from "react";
 
 import { CalendarDay } from "../classes";
-import type { MoveFocusBy, MoveFocusDir } from "../contexts/focus";
-import type { PropsContext } from "../contexts/props";
-import type { Mode } from "../types";
+import type { PropsContextValue } from "../contexts/props";
+import type { MoveFocusBy, MoveFocusDir } from "../types";
 
 import { getNextFocus } from "./getNextFocus";
 
 const props: Pick<
-  PropsContext<Mode, boolean>,
+  PropsContextValue,
   "disabled" | "hidden" | "startMonth" | "endMonth"
 > = {
   disabled: [],
diff --git a/src/helpers/getNextMonth.test.ts b/src/helpers/getNextMonth.test.ts
index dc18c9d76..ac4e8b3ce 100644
--- a/src/helpers/getNextMonth.test.ts
+++ b/src/helpers/getNextMonth.test.ts
@@ -9,6 +9,7 @@ describe("when number of months is 1", () => {
     const disableNavigation = true;
     it("the next month is undefined", () => {
       const result = getNextMonth(startingMonth, {
+        numberOfMonths: 1,
         disableNavigation,
         endMonth: undefined,
         startMonth: undefined
@@ -20,6 +21,7 @@ describe("when number of months is 1", () => {
     const endMonth = addMonths(startingMonth, 3);
     it("the next month is not undefined", () => {
       const result = getNextMonth(startingMonth, {
+        numberOfMonths: 1,
         endMonth,
         startMonth: undefined
       });
@@ -31,6 +33,7 @@ describe("when number of months is 1", () => {
     const endMonth = startingMonth;
     it("the next month is undefined", () => {
       const result = getNextMonth(startingMonth, {
+        numberOfMonths: 1,
         endMonth,
         startMonth: undefined
       });
diff --git a/src/helpers/getPossibleFocusDate.test.ts b/src/helpers/getPossibleFocusDate.test.ts
index 7e03f2060..78e8f67f5 100644
--- a/src/helpers/getPossibleFocusDate.test.ts
+++ b/src/helpers/getPossibleFocusDate.test.ts
@@ -11,15 +11,14 @@ import {
   endOfWeek
 } from "date-fns";
 
-import type { MoveFocusBy, MoveFocusDir } from "../contexts/focus";
-import type { PropsContext } from "../contexts/props";
-import type { Mode } from "../types";
+import type { PropsContextValue } from "../contexts/props";
+import type { MoveFocusBy, MoveFocusDir, Mode } from "../types";
 
 import { getPossibleFocusDate } from "./getPossibleFocusDate";
 
 const baseDate = new Date(2023, 0, 1); // Jan 1, 2023
 const options: Pick<
-  PropsContext<Mode, boolean>,
+  PropsContextValue,
   "locale" | "ISOWeek" | "weekStartsOn" | "startMonth" | "endMonth"
 > = {
   locale: undefined,
diff --git a/src/helpers/getPreviousMonth.test.ts b/src/helpers/getPreviousMonth.test.ts
index c8d568f03..ee00696b3 100644
--- a/src/helpers/getPreviousMonth.test.ts
+++ b/src/helpers/getPreviousMonth.test.ts
@@ -1,69 +1,57 @@
-import { addMonths, isSameMonth } from "date-fns";
-
 import { getPreviousMonth } from "./getPreviousMonth";
 
-const startingMonth = new Date(2020, 4, 31);
+it("should return undefined if navigation is disabled", () => {
+  const firstDisplayedMonth = new Date(2022, 0, 1); // January 2022
+  const props = {
+    disableNavigation: true,
+    pagedNavigation: false,
+    numberOfMonths: 1,
+    startMonth: new Date(2022, 0, 1)
+  };
+
+  const result = getPreviousMonth(firstDisplayedMonth, props);
 
-describe("when number of months is 1", () => {
-  describe("when the navigation is disabled", () => {
-    const disableNavigation = true;
-    it("the previous month is undefined", () => {
-      const result = getPreviousMonth(startingMonth, {
-        disableNavigation,
-        startMonth: undefined,
-        endMonth: undefined
-      });
-      expect(result).toBe(undefined);
-    });
-  });
-  describe("when in the navigable range", () => {
-    const startMonth = addMonths(startingMonth, -3);
-    it("the previous month is not undefined", () => {
-      const result = getPreviousMonth(startingMonth, {
-        startMonth,
-        endMonth: undefined
-      });
-      const expectedPrevMonth = addMonths(startingMonth, -1);
-      expect(result && isSameMonth(result, expectedPrevMonth)).toBeTruthy();
-    });
-  });
-  describe("when not in the navigable range", () => {
-    const startMonth = startingMonth;
-    it("the previous month is undefined", () => {
-      const result = getPreviousMonth(startingMonth, {
-        startMonth,
-        endMonth: undefined
-      });
-      expect(result).toBe(undefined);
-    });
-  });
+  expect(result).toBeUndefined();
 });
-describe("when displaying 3 months", () => {
-  const numberOfMonths = 3;
-  describe("when the navigation is paged", () => {
-    const pagedNavigation = true;
-    it("the previous month is 3 months back", () => {
-      const result = getPreviousMonth(startingMonth, {
-        numberOfMonths,
-        pagedNavigation,
-        startMonth: undefined,
-        endMonth: undefined
-      });
-      const expectedPrevMonth = addMonths(startingMonth, -numberOfMonths);
-      expect(result && isSameMonth(result, expectedPrevMonth)).toBeTruthy();
-    });
-  });
-  describe("when the navigation is not paged", () => {
-    const pagedNavigation = false;
-    it("the previous month is 1 months back", () => {
-      const result = getPreviousMonth(startingMonth, {
-        numberOfMonths,
-        pagedNavigation,
-        startMonth: undefined,
-        endMonth: undefined
-      });
-      const expectedPrevMonth = addMonths(startingMonth, -1);
-      expect(result && isSameMonth(result, expectedPrevMonth)).toBeTruthy();
-    });
-  });
+
+it("should return the previous month if startMonth is not provided", () => {
+  const firstDisplayedMonth = new Date(2022, 1, 1); // February 2022
+  const props = {
+    disableNavigation: false,
+    pagedNavigation: false,
+    numberOfMonths: 1,
+    startMonth: undefined
+  };
+
+  const result = getPreviousMonth(firstDisplayedMonth, props);
+
+  expect(result).toEqual(new Date(2022, 0, 1)); // January 2022
+});
+
+it("should return undefined if the previous month is before the startMonth", () => {
+  const firstDisplayedMonth = new Date(2022, 0, 1); // January 2022
+  const props = {
+    disableNavigation: false,
+    pagedNavigation: false,
+    numberOfMonths: 1,
+    startMonth: new Date(2022, 0, 1)
+  };
+
+  const result = getPreviousMonth(firstDisplayedMonth, props);
+
+  expect(result).toBeUndefined();
+});
+
+it("should return the correct previous month when pagedNavigation is true", () => {
+  const firstDisplayedMonth = new Date(2022, 2, 1); // March 2022
+  const props = {
+    disableNavigation: false,
+    pagedNavigation: true,
+    numberOfMonths: 2,
+    startMonth: new Date(2022, 0, 1)
+  };
+
+  const result = getPreviousMonth(firstDisplayedMonth, props);
+
+  expect(result).toEqual(new Date(2022, 0, 1)); // January 2022
 });
diff --git a/src/helpers/getStyleForModifiers.test.ts b/src/helpers/getStyleForModifiers.test.ts
index 0a33bc2e0..a0fc7666d 100644
--- a/src/helpers/getStyleForModifiers.test.ts
+++ b/src/helpers/getStyleForModifiers.test.ts
@@ -2,7 +2,7 @@ import type { CSSProperties } from "react";
 
 // Update the path as needed
 import { UI } from "../UI";
-import type { DayModifiers, ModifiersStyles, Styles } from "../types";
+import type { Modifiers, ModifiersStyles, Styles } from "../types";
 
 import { getStyleForModifiers } from "./getStyleForModifiers";
 
@@ -11,7 +11,7 @@ const baseDayStyle: CSSProperties = {
   color: "black"
 };
 const styles: Partial<Styles> = { [UI.Day]: baseDayStyle };
-const defaultModifiers: DayModifiers = {
+const defaultModifiers: Modifiers = {
   disabled: false,
   hidden: false,
   outside: false,
@@ -33,7 +33,7 @@ test("returns base style when no modifiers are provided", () => {
 });
 
 test("applies modifier styles to the base style", () => {
-  const dayModifiers: DayModifiers = {
+  const dayModifiers: Modifiers = {
     ...defaultModifiers,
     selected: true,
     disabled: false
@@ -52,7 +52,7 @@ test("applies modifier styles to the base style", () => {
 });
 
 test("ignores modifiers that are not active", () => {
-  const dayModifiers: DayModifiers = {
+  const dayModifiers: Modifiers = {
     ...defaultModifiers,
     selected: false,
     disabled: true
@@ -67,7 +67,7 @@ test("ignores modifiers that are not active", () => {
 });
 
 test("combines multiple active modifier styles", () => {
-  const dayModifiers: DayModifiers = {
+  const dayModifiers: Modifiers = {
     ...defaultModifiers,
     selected: true,
     highlighted: true
@@ -88,7 +88,7 @@ test("combines multiple active modifier styles", () => {
 });
 
 test("applies the most recent modifier style when there are conflicts", () => {
-  const dayModifiers: DayModifiers = {
+  const dayModifiers: Modifiers = {
     ...defaultModifiers,
     selected: true,
     highlighted: true
diff --git a/src/labels/labelDay.test.ts b/src/labels/labelDay.test.ts
index 2d84efffd..f66ea3214 100644
--- a/src/labels/labelDay.test.ts
+++ b/src/labels/labelDay.test.ts
@@ -1,11 +1,11 @@
 import { es } from "date-fns/locale/es";
 
-import type { DayModifiers } from "../types";
+import type { Modifiers } from "../types";
 
 import { labelDay } from "./labelDay";
 
 const day = new Date(2022, 10, 21);
-const dayModifiers: DayModifiers = {
+const dayModifiers: Modifiers = {
   disabled: false,
   focusable: false,
   focused: false,
diff --git a/src/types.test.tsx b/src/types.test.tsx
deleted file mode 100644
index 0c8b35fc8..000000000
--- a/src/types.test.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import React from "react";
-
-import { DayPicker } from "./DayPicker";
-
-const Test = () => {
-  return (
-    <>
-      <DayPicker />
-      <DayPicker mode="default" />
-      <DayPicker
-        mode="single"
-        selected={new Date()}
-        onSelect={(date: Date | undefined) => {}}
-      />
-      <DayPicker
-        mode="single"
-        required={true}
-        /* // @ts-expect-error Missing `selected` *. */
-        selected={undefined}
-        onSelect={(selected: Date, date: Date, modifiers) => {}}
-      />
-      {/** @ts-expect-error Wrong selected prop */}
-      <DayPicker mode="multi" selected={new Date()} />
-      <DayPicker
-        mode="multiple"
-        // @ts-expect-error Wrong `onSelect`
-        onSelect={(date: Date) => {}}
-      />
-
-      {/** @ts-expect-error Extra `selected` */}
-      <DayPicker mode="default" selected={new Date()} />
-
-      <DayPicker
-        mode="default"
-        modifiers={{ selected: new Date() }}
-        onDayClick={() => {}}
-      />
-      <DayPicker
-        mode="default"
-        // @ts-expect-error Extra `selected`
-        selected={new Date()}
-        onDayClick={() => {}}
-      />
-      {/* <DayPicker mode="custom" selected={undefined} /> */}
-      {/* <DayPicker mode="custom" onSelect={() => console.log(1)} /> */}
-    </>
-  );
-};
-
-it("should type-check", () => {
-  expect(true).toBeTruthy();
-});
diff --git a/src/types/props.test.tsx b/src/types/props.test.tsx
new file mode 100644
index 000000000..7b6f7d0e9
--- /dev/null
+++ b/src/types/props.test.tsx
@@ -0,0 +1,64 @@
+import React from "react";
+
+import { DayPicker } from "../DayPicker";
+
+const Test = () => {
+  return (
+    <>
+      <DayPicker />
+      <DayPicker mode="single" />
+      <DayPicker
+        mode="single"
+        selected={undefined}
+        onSelect={(date: Date | undefined) => {}}
+      />
+      <DayPicker
+        mode="single"
+        selected={new Date()}
+        onSelect={(date: Date | undefined) => {}}
+      />
+      {/* @ts-expect-error Missing `selected` */}
+      <DayPicker
+        mode="single"
+        required
+        onSelect={(date: Date | undefined) => {}}
+      />
+      {/* @ts-expect-error selected should not be undefined. */}
+      <DayPicker mode="single" required selected={undefined} />
+      <DayPicker
+        mode="multiple"
+        required={true}
+        // @ts-expect-error Missing `selected`
+        selected={undefined}
+        onSelect={(selected: Date[], date: Date, modifiers) => {}}
+      />
+      <DayPicker
+        mode="multiple"
+        required={false}
+        selected={undefined}
+        // @ts-expect-error Selected can be also undefined
+        onSelect={(selected: Date[], date: Date, modifiers) => {}}
+      />
+      {/** @ts-expect-error Wrong selected prop */}
+      <DayPicker mode="multiple" selected={new Date()} />
+      <DayPicker mode="multiple" onSelect={(date: Date[] | undefined) => {}} />
+      <DayPicker
+        mode="multiple"
+        required
+        selected={[]}
+        onSelect={(date: Date[]) => {}}
+      />
+      <DayPicker mode="single" selected={new Date()} />
+      <DayPicker modifiers={{ selected: new Date() }} onDayClick={() => {}} />
+      <DayPicker
+        onSelect={() => {}}
+        selected={new Date()}
+        onDayClick={() => {}}
+      />
+    </>
+  );
+};
+
+it("should type-check", () => {
+  expect(true).toBeTruthy();
+});
diff --git a/src/utils/addToRange.test.ts b/src/utils/addToRange.test.ts
index f51734d38..830df5bd9 100644
--- a/src/utils/addToRange.test.ts
+++ b/src/utils/addToRange.test.ts
@@ -58,7 +58,7 @@ describe('when "from", "to" and "day" are the same', () => {
     result = addToRange(day, range);
   });
   test("should return an undefined range (reset)", () => {
-    expect(result).toBeUndefined();
+    expect(result).toEqual({ from: undefined, to: undefined });
   });
 });
 
@@ -85,8 +85,8 @@ describe('when "from" and "day" are the same', () => {
   beforeAll(() => {
     result = addToRange(day, range);
   });
-  test("should return an undefined range (reset)", () => {
-    expect(result).toBeUndefined();
+  test("should reset the range", () => {
+    expect(result).toEqual({ from: undefined, to: undefined });
   });
 });
 
diff --git a/src/utils/isDateInRange.test.ts b/src/utils/isDateInRange.test.ts
index b55674ad1..db33f14b2 100644
--- a/src/utils/isDateInRange.test.ts
+++ b/src/utils/isDateInRange.test.ts
@@ -1,4 +1,4 @@
-import { addDays } from "date-fns";
+import { addDays } from "date-fns/addDays";
 
 import { DateRange } from "../types";
 
diff --git a/test/render.tsx b/test/render.tsx
index 252b281c1..11131105e 100644
--- a/test/render.tsx
+++ b/test/render.tsx
@@ -1,16 +1,16 @@
 import React, { ReactElement } from "react";
 
 import { render as testingLibraryRender } from "@testing-library/react";
-import type { Mode, DayPickerProps } from "react-day-picker";
+import type { DayPickerProps } from "react-day-picker";
 
-import { ContextProviders } from "../src/contexts/root";
+import { ContextProviders } from "../src/contexts/providers";
 
 /** Render a React Element wrapped with the Root Provider. */
 export function render(
   /** The element to render. */
   element: ReactElement,
   /** The initial DayPicker props to pass to the Root Provider. */
-  context?: DayPickerProps<Mode>,
+  context?: DayPickerProps,
   /** The options to pass to the testing library render function. */
   options?: Parameters<typeof testingLibraryRender>[1]
 ): ReturnType<typeof testingLibraryRender> {
diff --git a/test/renderHook.tsx b/test/renderHook.tsx
index 9a6020593..a9647e89b 100644
--- a/test/renderHook.tsx
+++ b/test/renderHook.tsx
@@ -1,16 +1,17 @@
 import React from "react";
 
 import { renderHook as testingLibraryRenderHook } from "@testing-library/react";
+import { DayPickerProps } from "react-day-picker";
 
-import { ContextProviders } from "../src/contexts/root";
-import { DayPickerProps, Mode } from "../src/types";
+import { ContextProviders } from "../src/contexts/providers";
+import { Mode } from "../src/types";
 
 /** Render a hook wrapped with the {@link ContextProviders} Provider. */
 export function renderHook<TResult>(
   /** The hook to render. */
   hook: () => TResult,
   /** The props to pass to the {@link ContextProviders}. */
-  props?: DayPickerProps<Mode>,
+  props?: DayPickerProps,
   /** The options to pass to the testing library render function. */
   options?: Omit<Parameters<typeof testingLibraryRenderHook>[1], "wrapper">
 ) {