Repository: gpbl/react-day-picker
Pull request #2279 (2024-07-20 17:26:43)

chore: clean up types for release


Patch:
diff --git a/examples/CustomWeek.tsx b/examples/CustomWeek.tsx
index 50a38c53f..ec5217d16 100644
--- a/examples/CustomWeek.tsx
+++ b/examples/CustomWeek.tsx
@@ -1,7 +1,7 @@
 import React, { useState } from "react";
 
 import { endOfWeek, startOfWeek } from "date-fns";
-import { DateRange, DayPicker } from "react-day-picker";
+import { DateRange, DayPicker, rangeIncludesDate } from "react-day-picker";
 
 /** Select the whole week when the day is clicked. */
 export function CustomWeek() {
@@ -10,8 +10,13 @@ export function CustomWeek() {
   return (
     <DayPicker
       showWeekNumber
+      showOutsideDays
       modifiers={{
-        selected: selectedWeek
+        selected: selectedWeek,
+        range_start: selectedWeek?.from,
+        range_end: selectedWeek?.to,
+        range_middle: (date: Date) =>
+          selectedWeek ? rangeIncludesDate(selectedWeek, date, true) : false
       }}
       onDayClick={(day, modifiers) => {
         if (modifiers.selected) {
diff --git a/src/DayPicker.tsx b/src/DayPicker.tsx
index 6e08503ff..fd5437a37 100644
--- a/src/DayPicker.tsx
+++ b/src/DayPicker.tsx
@@ -13,25 +13,28 @@ import { getComponents } from "./helpers/getComponents.js";
 import { getDataAttributes } from "./helpers/getDataAttributes.js";
 import { getDateLib } from "./helpers/getDateLib.js";
 import { getDefaultClassNames } from "./helpers/getDefaultClassNames.js";
-import { getDropdownMonths } from "./helpers/getDropdownMonths.js";
-import { getDropdownYears } from "./helpers/getDropdownYears.js";
 import { getFormatters } from "./helpers/getFormatters.js";
+import { getMonthOptions } from "./helpers/getMonthOptions.js";
 import { getStyleForModifiers } from "./helpers/getStyleForModifiers.js";
 import { getWeekdays } from "./helpers/getWeekdays.js";
+import { getYearOptions } from "./helpers/getYearOptions.js";
 import * as defaultLabels from "./labels/index.js";
-import { FormatOptions, LabelOptions } from "./lib/dateLib.js";
-import { UseRange } from "./selection/useRange.js";
+import type { FormatOptions, LabelOptions } from "./lib/dateLib.js";
 import type {
   DayPickerProps,
   Modifiers,
   MoveFocusBy,
-  MoveFocusDir
+  MoveFocusDir,
+  SelectedValue,
+  SelectHandler
 } from "./types/index.js";
 import { useCalendar } from "./useCalendar.js";
-import { dayPickerContext } from "./useDayPicker.js";
+import { type DayPickerContext, dayPickerContext } from "./useDayPicker.js";
 import { useFocus } from "./useFocus.js";
-import { useModifiers } from "./useModifiers.js";
+import { useGetModifiers } from "./useGetModifiers.js";
 import { useSelection } from "./useSelection.js";
+import { rangeIncludesDate } from "./utils/rangeIncludesDate.js";
+import { isDateRange } from "./utils/typeguards.js";
 
 /**
  * Render the date picker calendar.
@@ -39,49 +42,52 @@ import { useSelection } from "./useSelection.js";
  * @group DayPicker
  * @see http://daypicker.dev
  */
-export function DayPicker(props: DayPickerProps) {
+export function DayPicker<T extends DayPickerProps>(props: T) {
+  const { components, formatters, labels, dateLib, classNames } = useMemo(
+    () => ({
+      dateLib: getDateLib(props.dateLib),
+      components: getComponents(props.components),
+      formatters: getFormatters(props.formatters),
+      labels: { ...defaultLabels, ...props.labels },
+      classNames: { ...getDefaultClassNames(), ...props.classNames }
+    }),
+    [
+      props.classNames,
+      props.components,
+      props.dateLib,
+      props.formatters,
+      props.labels
+    ]
+  );
+
   const {
     captionLayout,
+    firstWeekContainsDate,
     locale,
     mode,
-    numberOfMonths = 1,
     onDayBlur,
     onDayClick,
     onDayFocus,
     onDayKeyDown,
-    onPrevClick,
     onNextClick,
+    onPrevClick,
     showWeekNumber,
-    styles
+    styles,
+    useAdditionalDayOfYearTokens,
+    useAdditionalWeekYearTokens,
+    weekStartsOn
   } = props;
 
   const formatOptions: FormatOptions = {
     locale,
-    weekStartsOn: props.weekStartsOn,
-    firstWeekContainsDate: props.firstWeekContainsDate,
-    useAdditionalWeekYearTokens: props.useAdditionalWeekYearTokens,
-    useAdditionalDayOfYearTokens: props.useAdditionalDayOfYearTokens
+    weekStartsOn,
+    firstWeekContainsDate,
+    useAdditionalWeekYearTokens,
+    useAdditionalDayOfYearTokens
   };
 
   const labelOptions: LabelOptions = formatOptions;
 
-  const { components, formatters, labels, dateLib, classNames } = useMemo(
-    () => ({
-      dateLib: getDateLib(props.dateLib),
-      components: getComponents(props.components),
-      formatters: getFormatters(props.formatters),
-      labels: { ...defaultLabels, ...props.labels },
-      classNames: { ...getDefaultClassNames(), ...props.classNames }
-    }),
-    [
-      props.classNames,
-      props.components,
-      props.dateLib,
-      props.formatters,
-      props.labels
-    ]
-  );
-
   const {
     formatCaption,
     formatDay,
@@ -93,30 +99,32 @@ export function DayPicker(props: DayPickerProps) {
   } = formatters;
 
   const calendar = useCalendar(props, dateLib);
+
   const {
+    days,
     months,
-    navigationStartMonth,
-    navigationEndMonth,
+    navStart,
+    navEnd,
     previousMonth,
     nextMonth,
-    goToPreviousMonth,
-    goToNextMonth,
     goToMonth
   } = calendar;
 
-  const modifiers = useModifiers(props, calendar, dateLib);
-
-  const selection = useSelection(props, dateLib);
-  const { isSelected, handleSelect } = selection;
+  const getModifiers = useGetModifiers(days, props, dateLib);
 
-  const focus = useFocus(props, calendar, modifiers, selection, dateLib);
   const {
-    isFocusTarget,
-    focused: focusedDay,
-    setFocused,
-    moveFocus,
-    blur
-  } = focus;
+    isSelected,
+    select,
+    selected: selectedValue
+  } = useSelection<T>(props, dateLib) ?? {};
+
+  const { blur, focused, isFocusTarget, moveFocus, setFocused } = useFocus<T>(
+    props,
+    calendar,
+    getModifiers,
+    isSelected ?? (() => false),
+    dateLib
+  );
 
   const {
     labelDayButton,
@@ -141,25 +149,25 @@ export function DayPicker(props: DayPickerProps) {
 
   const handlePreviousClick = useCallback(() => {
     if (!previousMonth) return;
-    goToPreviousMonth();
+    goToMonth(previousMonth);
     onPrevClick?.(previousMonth);
-  }, [goToPreviousMonth, onPrevClick, previousMonth]);
+  }, [previousMonth, goToMonth, onPrevClick]);
 
   const handleNextClick = useCallback(() => {
     if (!nextMonth) return;
-    goToNextMonth();
+    goToMonth(nextMonth);
     onNextClick?.(nextMonth);
-  }, [goToNextMonth, nextMonth, onNextClick]);
+  }, [goToMonth, nextMonth, onNextClick]);
 
   const handleDayClick = useCallback(
     (day: CalendarDay, m: Modifiers) => (e: MouseEvent) => {
       e.preventDefault();
       e.stopPropagation();
-      handleSelect(day.date, m, e);
       setFocused(day);
+      select?.(day.date, m, e);
       onDayClick?.(day.date, m, e);
     },
-    [handleSelect, onDayClick, setFocused]
+    [select, onDayClick, setFocused]
   );
 
   const handleDayFocus = useCallback(
@@ -211,12 +219,18 @@ export function DayPicker(props: DayPickerProps) {
     [classNames, props.className, props.style, styles]
   );
 
-  const dataAttributes = useMemo(() => getDataAttributes(props), [props]);
+  const dataAttributes = getDataAttributes(props);
 
-  const contextValue = useMemo(
-    () => ({ ...calendar, ...selection, ...modifiers }),
-    [calendar, modifiers, selection]
-  );
+  const contextValue: DayPickerContext<T> = {
+    selected: selectedValue as SelectedValue<T>,
+    select: select as SelectHandler<T>,
+    isSelected,
+    months,
+    nextMonth,
+    previousMonth,
+    goToMonth,
+    getModifiers
+  };
 
   return (
     <dayPickerContext.Provider value={contextValue}>
@@ -228,9 +242,6 @@ export function DayPicker(props: DayPickerProps) {
         lang={props.lang}
         nonce={props.nonce}
         title={props.title}
-        data-interactive={isInteractive || undefined}
-        data-multiple-months={numberOfMonths > 1 || undefined}
-        data-week-numbers={showWeekNumber || undefined}
         {...dataAttributes}
       >
         <components.Months
@@ -247,8 +258,8 @@ export function DayPicker(props: DayPickerProps) {
               <components.Button
                 type="button"
                 className={classNames[UI.ButtonPrevious]}
-                tabIndex={calendar.previousMonth ? undefined : -1}
-                disabled={calendar.previousMonth ? undefined : true}
+                tabIndex={previousMonth ? undefined : -1}
+                disabled={previousMonth ? undefined : true}
                 aria-label={labelPrevious(previousMonth, labelOptions)}
                 onClick={handlePreviousClick}
               >
@@ -298,18 +309,19 @@ export function DayPicker(props: DayPickerProps) {
               goToMonth(month);
             };
 
-            const dropdownMonths = getDropdownMonths(
+            const dropdownMonths = getMonthOptions(
               calendarMonth.date,
-              navigationStartMonth,
-              navigationEndMonth,
+              navStart,
+              navEnd,
               formatters,
               locale,
               dateLib
             );
-            const dropdownYears = getDropdownYears(
+
+            const dropdownYears = getYearOptions(
               months[0].date,
-              navigationStartMonth,
-              navigationEndMonth,
+              navStart,
+              navEnd,
               formatters,
               dateLib
             );
@@ -454,49 +466,50 @@ export function DayPicker(props: DayPickerProps) {
                           )}
                           {week.days.map((day: CalendarDay) => {
                             const { date } = day;
-                            const dayModifiers = modifiers.getModifiers(day);
-                            const focused = focusedDay?.isEqualTo(day);
-
-                            if (!dayModifiers.hidden && focused)
-                              dayModifiers[DayFlag.focused] = true;
-
-                            const selected =
-                              isSelected(date) || dayModifiers.selected;
-
-                            dayModifiers[SelectionState.selected] =
-                              !dayModifiers.disabled && selected;
-
-                            if (!dayModifiers.disabled && mode === "range") {
-                              const range = selection as UseRange<false>;
-                              dayModifiers[SelectionState.range_start] =
-                                range.isRangeStart(date);
-                              dayModifiers[SelectionState.range_end] =
-                                range.isRangeEnd(date);
-                              dayModifiers[SelectionState.range_middle] =
-                                range.isRangeMiddle(date);
+                            const modifiers = getModifiers(day);
+
+                            modifiers[DayFlag.focused] =
+                              !modifiers.hidden &&
+                              Boolean(focused?.isEqualTo(day));
+
+                            modifiers[SelectionState.selected] =
+                              !modifiers.disabled &&
+                              (isSelected?.(date) || modifiers.selected);
+
+                            if (isDateRange(selectedValue)) {
+                              // add range modifiers
+                              const { from, to } = selectedValue;
+                              modifiers[SelectionState.range_start] = Boolean(
+                                from && to && dateLib.isSameDay(date, from)
+                              );
+                              modifiers[SelectionState.range_end] = Boolean(
+                                from && to && dateLib.isSameDay(date, to)
+                              );
+                              modifiers[SelectionState.range_middle] =
+                                rangeIncludesDate(
+                                  selectedValue,
+                                  date,
+                                  true,
+                                  dateLib
+                                );
                             }
 
-                            const style = {
-                              ...getStyleForModifiers(
-                                dayModifiers,
-                                props.modifiersStyles
-                              ),
-                              ...styles?.[UI.Day]
-                            };
+                            const style = getStyleForModifiers(
+                              modifiers,
+                              styles,
+                              props.modifiersStyles
+                            );
 
-                            const className = [
-                              classNames[UI.Day],
-                              ...getClassNamesForModifiers(
-                                dayModifiers,
-                                classNames,
-                                props.modifiersClassNames
-                              )
-                            ];
+                            const className = getClassNamesForModifiers(
+                              modifiers,
+                              classNames,
+                              props.modifiersClassNames
+                            );
 
                             const ariaLabel = !isInteractive
                               ? labelGridcell(
                                   date,
-                                  dayModifiers,
+                                  modifiers,
                                   labelOptions,
                                   dateLib
                                 )
@@ -512,14 +525,12 @@ export function DayPicker(props: DayPickerProps) {
                               <components.Day
                                 key={`${dateLib.format(date, "yyyy-MM-dd")}_${dateLib.format(day.displayMonth, "yyyy-MM")}`}
                                 day={day}
-                                modifiers={dayModifiers}
+                                modifiers={modifiers}
                                 role="gridcell"
                                 className={className.join(" ")}
                                 style={style}
-                                aria-hidden={dayModifiers.hidden || undefined}
-                                aria-selected={
-                                  dayModifiers.selected || undefined
-                                }
+                                aria-hidden={modifiers.hidden || undefined}
+                                aria-selected={modifiers.selected || undefined}
                                 aria-label={ariaLabel}
                                 {...dataAttributes}
                               >
@@ -528,24 +539,19 @@ export function DayPicker(props: DayPickerProps) {
                                     className={classNames[UI.DayButton]}
                                     style={styles?.[UI.DayButton]}
                                     day={day}
-                                    modifiers={dayModifiers}
-                                    disabled={
-                                      dayModifiers.disabled || undefined
-                                    }
+                                    modifiers={modifiers}
+                                    disabled={modifiers.disabled || undefined}
                                     tabIndex={isFocusTarget(day) ? 0 : -1}
                                     aria-label={labelDayButton(
                                       date,
-                                      dayModifiers,
+                                      modifiers,
                                       labelOptions,
                                       dateLib
                                     )}
-                                    onClick={handleDayClick(day, dayModifiers)}
-                                    onBlur={handleDayBlur(day, dayModifiers)}
-                                    onFocus={handleDayFocus(day, dayModifiers)}
-                                    onKeyDown={handleDayKeyDown(
-                                      day,
-                                      dayModifiers
-                                    )}
+                                    onClick={handleDayClick(day, modifiers)}
+                                    onBlur={handleDayBlur(day, modifiers)}
+                                    onFocus={handleDayFocus(day, modifiers)}
+                                    onKeyDown={handleDayKeyDown(day, modifiers)}
                                   >
                                     {formatDay(date, formatOptions, dateLib)}
                                   </components.DayButton>
diff --git a/src/classes/CalendarDay.ts b/src/classes/CalendarDay.ts
index ad615df08..6096cb116 100644
--- a/src/classes/CalendarDay.ts
+++ b/src/classes/CalendarDay.ts
@@ -21,10 +21,6 @@ export class CalendarDay {
       displayMonth && !dateLib.isSameMonth(date, displayMonth)
     );
     this.dateLib = dateLib;
-    const { format } = dateLib;
-    this.uid =
-      format(date, "yyyyMMdd") +
-      (this.outside ? `-` + format(displayMonth, "yyyyMMdd") : "");
   }
 
   /**
@@ -54,9 +50,6 @@ export class CalendarDay {
   /** The date represented by this day. */
   readonly date: Date;
 
-  /** A unique identifier for the day. */
-  readonly uid: string;
-
   /**
    * Check if the day is the same as the given day: considering if it is in the
    * same display month.
diff --git a/src/components/Button.tsx b/src/components/Button.tsx
index 0a9707f6c..bba56e1e0 100644
--- a/src/components/Button.tsx
+++ b/src/components/Button.tsx
@@ -3,8 +3,6 @@ import React from "react";
 /**
  * Render the button elements in the calendar.
  *
- * Use the `components` prop to swap this component with a custom one.
- *
  * @group Components
  * @see https://daypicker.dev/next/guides/custom-components
  */
diff --git a/src/components/CaptionLabel.tsx b/src/components/CaptionLabel.tsx
index d024ae76a..24ba39d6a 100644
--- a/src/components/CaptionLabel.tsx
+++ b/src/components/CaptionLabel.tsx
@@ -3,8 +3,6 @@ import React from "react";
 /**
  * Render the label in the month caption.
  *
- * Use the `components` prop to swap this component with a custom one.
- *
  * @group Components
  * @see https://daypicker.dev/next/guides/custom-components
  */
diff --git a/src/components/Chevron.tsx b/src/components/Chevron.tsx
index 9bfbd2a45..00923a1e8 100644
--- a/src/components/Chevron.tsx
+++ b/src/components/Chevron.tsx
@@ -3,8 +3,6 @@ import React from "react";
 /**
  * Render the chevron icon used in the navigation buttons and dropdowns.
  *
- * Use the `components` prop to swap this component with a custom one.
- *
  * @group Components
  * @see https://daypicker.dev/next/guides/custom-components
  */
diff --git a/src/components/Day.tsx b/src/components/Day.tsx
index 7835b4e85..1811dc562 100644
--- a/src/components/Day.tsx
+++ b/src/components/Day.tsx
@@ -7,8 +7,7 @@ import type { Modifiers } from "../types/index.js";
  * Render the gridcell of a day in the calendar and handle the interaction and
  * the focus with they day.
  *
- * Use the `components` prop to swap this component with a custom one. If you
- * need to just change the content of the day cell, consider swapping the
+ * If you need to just change the content of the day cell, consider swapping the
  * `DayDate` component instead.
  *
  * @group Components
@@ -16,7 +15,9 @@ import type { Modifiers } from "../types/index.js";
  */
 export function Day(
   props: {
+    /** The day to render. */
     day: CalendarDay;
+    /** The modifiers to apply to the day. */
     modifiers: Modifiers;
   } & JSX.IntrinsicElements["td"]
 ) {
diff --git a/src/components/DayButton.tsx b/src/components/DayButton.tsx
index 5e29fa005..70adcabc3 100644
--- a/src/components/DayButton.tsx
+++ b/src/components/DayButton.tsx
@@ -9,8 +9,6 @@ import type { Modifiers } from "../types/index.js";
  * When not interactive, DayPicker will render a `DayContent` component instead
  * of a `DayButton` component.
  *
- * Use the `components` prop to swap this component with a custom one.
- *
  * @group Components
  * @see https://daypicker.dev/next/guides/custom-components
  */
diff --git a/src/components/Dropdown.tsx b/src/components/Dropdown.tsx
index f33e59046..1b41ac954 100644
--- a/src/components/Dropdown.tsx
+++ b/src/components/Dropdown.tsx
@@ -19,17 +19,12 @@ export type DropdownOption = {
 /**
  * Render a dropdown component to use in the navigation bar.
  *
- * Use the `components` prop to swap this component with a custom one.
- *
  * @group Components
  * @see https://daypicker.dev/next/guides/custom-components
  */
 export function Dropdown(
   props: {
-    components: Pick<
-      Required<CustomComponents>,
-      "Select" | "Option" | "Chevron"
-    >;
+    components: Pick<CustomComponents, "Select" | "Option" | "Chevron">;
     classNames: Pick<
       ClassNames,
       UI.DropdownRoot | UI.Dropdown | UI.CaptionLabel | UI.Chevron
diff --git a/src/components/DropdownNav.tsx b/src/components/DropdownNav.tsx
index 1ff599295..16445bafd 100644
--- a/src/components/DropdownNav.tsx
+++ b/src/components/DropdownNav.tsx
@@ -3,8 +3,6 @@ import React from "react";
 /**
  * Render the the navigation dropdowns.
  *
- * Use the `components` prop to swap this component with a custom one.
- *
  * @group Components
  * @see https://daypicker.dev/next/guides/custom-components
  */
diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx
index d113916eb..fbb4e7026 100644
--- a/src/components/Footer.tsx
+++ b/src/components/Footer.tsx
@@ -3,8 +3,6 @@ import React from "react";
 /**
  * Component wrapping the footer.
  *
- * Use the `components` prop to swap this component with a custom one.
- *
  * @group Components
  * @see https://daypicker.dev/next/guides/custom-components
  */
diff --git a/src/components/Month.tsx b/src/components/Month.tsx
index 0d99c27d6..0f98b9097 100644
--- a/src/components/Month.tsx
+++ b/src/components/Month.tsx
@@ -6,8 +6,6 @@ import type { CalendarMonth } from "../classes/CalendarMonth.js";
  * Render the grid with the weekday header row and the weeks for the given
  * month.
  *
- * Use the `components` prop to swap this component with a custom one.
- *
  * @group Components
  * @see https://daypicker.dev/next/guides/custom-components
  */
diff --git a/src/components/MonthCaption.tsx b/src/components/MonthCaption.tsx
index 1d6a80753..1fae73492 100644
--- a/src/components/MonthCaption.tsx
+++ b/src/components/MonthCaption.tsx
@@ -5,8 +5,6 @@ import type { CalendarMonth } from "../classes/index.js";
 /**
  * Render the caption of a month in the calendar.
  *
- * Use the `components` prop to swap this component with a custom one.
- *
  * @group Components
  * @see https://daypicker.dev/next/guides/custom-components
  */
diff --git a/src/components/MonthGrid.tsx b/src/components/MonthGrid.tsx
index e8db98b26..85f392d19 100644
--- a/src/components/MonthGrid.tsx
+++ b/src/components/MonthGrid.tsx
@@ -3,8 +3,6 @@ import React from "react";
 /**
  * Render the grid of days in a month.
  *
- * Use the `components` prop to swap this component with a custom one.
- *
  * @group Components
  * @see https://daypicker.dev/next/guides/custom-components
  */
diff --git a/src/components/Nav.tsx b/src/components/Nav.tsx
index fc26f300f..ed0eff844 100644
--- a/src/components/Nav.tsx
+++ b/src/components/Nav.tsx
@@ -3,8 +3,6 @@ import React from "react";
 /**
  * Render the toolbar with the navigation button.
  *
- * Use the `components` prop to swap this component with a custom one.
- *
  * @group Components
  * @see https://daypicker.dev/next/guides/custom-components
  */
diff --git a/src/components/Option.tsx b/src/components/Option.tsx
index c97a23700..452a47388 100644
--- a/src/components/Option.tsx
+++ b/src/components/Option.tsx
@@ -3,8 +3,6 @@ import React from "react";
 /**
  * Render the `option` element.
  *
- * Use the `components` prop to swap this component with a custom one.
- *
  * @group Components
  * @see https://daypicker.dev/next/guides/custom-components
  */
diff --git a/src/components/Root.tsx b/src/components/Root.tsx
index 5c2dc79b8..107985c98 100644
--- a/src/components/Root.tsx
+++ b/src/components/Root.tsx
@@ -3,8 +3,6 @@ import React from "react";
 /**
  * Render the root element.
  *
- * Use the `components` prop to swap this component with a custom one.
- *
  * @group Components
  * @see https://daypicker.dev/next/guides/custom-components
  */
diff --git a/src/components/Select.tsx b/src/components/Select.tsx
index 93a8da9ef..944b8687a 100644
--- a/src/components/Select.tsx
+++ b/src/components/Select.tsx
@@ -3,8 +3,6 @@ import React from "react";
 /**
  * Render the `select` element.
  *
- * Use the `components` prop to swap this component with a custom one.
- *
  * @group Components
  * @see https://daypicker.dev/next/guides/custom-components
  */
diff --git a/src/components/Week.tsx b/src/components/Week.tsx
index 3b14f0842..e2809ac70 100644
--- a/src/components/Week.tsx
+++ b/src/components/Week.tsx
@@ -5,8 +5,6 @@ import type { CalendarWeek } from "../classes/index.js";
 /**
  * Render a row in the calendar, with the days and the week number.
  *
- * Use the `components` prop to swap this component with a custom one.
- *
  * @group Components
  * @see https://daypicker.dev/next/guides/custom-components
  */
diff --git a/src/components/WeekNumber.tsx b/src/components/WeekNumber.tsx
index a0ba218e9..f0471491f 100644
--- a/src/components/WeekNumber.tsx
+++ b/src/components/WeekNumber.tsx
@@ -5,8 +5,6 @@ import type { CalendarWeek } from "../classes/index.js";
 /**
  * Render the cell with the number of the week.
  *
- * Use the `components` prop to swap this component with a custom one.
- *
  * @group Components
  * @see https://daypicker.dev/next/guides/custom-components
  */
diff --git a/src/components/WeekNumberHeader.tsx b/src/components/WeekNumberHeader.tsx
index 52c8d5afd..655bf997b 100644
--- a/src/components/WeekNumberHeader.tsx
+++ b/src/components/WeekNumberHeader.tsx
@@ -3,8 +3,6 @@ import React from "react";
 /**
  * Render the column header for the week numbers.
  *
- * Use the `components` prop to swap this component with a custom one.
- *
  * @group Components
  * @see https://daypicker.dev/next/guides/custom-components
  */
diff --git a/src/components/Weekday.tsx b/src/components/Weekday.tsx
index 1fdebf4fc..6f4e2d40d 100644
--- a/src/components/Weekday.tsx
+++ b/src/components/Weekday.tsx
@@ -3,8 +3,6 @@ import React from "react";
 /**
  * Render the column header with the weekday name (e.g. "Mo", "Tu", etc.).
  *
- * Use the `components` prop to swap this component with a custom one.
- *
  * @group Components
  * @see https://daypicker.dev/next/guides/custom-components
  */
diff --git a/src/components/Weekdays.tsx b/src/components/Weekdays.tsx
index f374f189c..e4aed2362 100644
--- a/src/components/Weekdays.tsx
+++ b/src/components/Weekdays.tsx
@@ -3,8 +3,6 @@ import React from "react";
 /**
  * Render the row with the weekday names.
  *
- * Use the `components` prop to swap this component with a custom one.
- *
  * @group Components
  * @see https://daypicker.dev/next/guides/custom-components
  */
diff --git a/src/components/Weeks.tsx b/src/components/Weeks.tsx
index fef6d7f80..02bed18cd 100644
--- a/src/components/Weeks.tsx
+++ b/src/components/Weeks.tsx
@@ -1,9 +1,7 @@
 import React from "react";
 
 /**
- * Render the label in the month caption.
- *
- * Use the `components` prop to swap this component with a custom one.
+ * Render the weeks in the month grid.
  *
  * @group Components
  * @see https://daypicker.dev/next/guides/custom-components
diff --git a/src/formatters/formatMonthDropdown.ts b/src/formatters/formatMonthDropdown.ts
index e6aa008ae..83620aa00 100644
--- a/src/formatters/formatMonthDropdown.ts
+++ b/src/formatters/formatMonthDropdown.ts
@@ -4,7 +4,7 @@ import { enUS } from "../lib/locales.js";
 /**
  * Format the month number for the dropdown option label.
  *
- * @defaultValue `monthNumber.toString()`
+ * @defaultValue The localized month name
  * @group Formatters
  * @see https://daypicker.dev/next/docs/translation#custom-formatters
  */
@@ -14,8 +14,5 @@ export function formatMonthDropdown(
   /** The locale to use for formatting. */
   locale = enUS
 ): string {
-  return (
-    locale.localize?.month(monthNumber as DateFnsMonth) ??
-    monthNumber.toString()
-  );
+  return locale.localize?.month(monthNumber as DateFnsMonth);
 }
diff --git a/src/helpers/calculateFocusTarget.ts b/src/helpers/calculateFocusTarget.ts
index 343454eed..e082954be 100644
--- a/src/helpers/calculateFocusTarget.ts
+++ b/src/helpers/calculateFocusTarget.ts
@@ -1,10 +1,9 @@
 import { DayFlag } from "../UI.js";
 import type { CalendarDay } from "../classes/index.js";
 import type { Modifiers } from "../types/index.js";
-import { UseCalendar } from "../useCalendar.js";
 
 export function calculateFocusTarget(
-  calendar: UseCalendar,
+  days: CalendarDay[],
   getModifiers: (day: CalendarDay) => Modifiers,
   isSelected: (date: Date) => boolean,
   lastFocused: CalendarDay | undefined
@@ -14,12 +13,16 @@ export function calculateFocusTarget(
   let index = 0;
   let found = false;
 
-  while (index < calendar.days.length && !found) {
-    const day = calendar.days[index];
-    const m = getModifiers(day);
+  while (index < days.length && !found) {
+    const day = days[index];
+    const modifiers = getModifiers(day);
 
-    if (!m[DayFlag.disabled] && !m[DayFlag.hidden] && !m[DayFlag.outside]) {
-      if (m[DayFlag.focused]) {
+    if (
+      !modifiers[DayFlag.disabled] &&
+      !modifiers[DayFlag.hidden] &&
+      !modifiers[DayFlag.outside]
+    ) {
+      if (modifiers[DayFlag.focused]) {
         focusTarget = day;
         found = true;
       } else if (lastFocused?.isEqualTo(day)) {
@@ -28,7 +31,7 @@ export function calculateFocusTarget(
       } else if (isSelected(day.date)) {
         focusTarget = day;
         found = true;
-      } else if (m[DayFlag.today]) {
+      } else if (modifiers[DayFlag.today]) {
         focusTarget = day;
         found = true;
       }
@@ -39,7 +42,7 @@ export function calculateFocusTarget(
 
   if (!focusTarget) {
     // return the first day that is focusable
-    focusTarget = calendar.days.find((day) => {
+    focusTarget = days.find((day) => {
       const m = getModifiers(day);
       return !m[DayFlag.disabled] && !m[DayFlag.hidden] && !m[DayFlag.outside];
     });
diff --git a/src/helpers/getClassNamesForModifiers.ts b/src/helpers/getClassNamesForModifiers.ts
index 055ff04d0..580b3547c 100644
--- a/src/helpers/getClassNamesForModifiers.ts
+++ b/src/helpers/getClassNamesForModifiers.ts
@@ -1,4 +1,4 @@
-import { DayFlag, SelectionState } from "../UI.js";
+import { DayFlag, SelectionState, UI } from "../UI.js";
 import type { ModifiersClassNames, ClassNames } from "../types/index.js";
 
 export function getClassNamesForModifiers(
@@ -8,16 +8,19 @@ export function getClassNamesForModifiers(
 ) {
   const modifierClassNames = Object.entries(modifiers)
     .filter(([, active]) => active === true)
-    .reduce((previousValue, [key]) => {
-      if (modifiersClassNames[key]) {
-        previousValue.push(modifiersClassNames[key as string]);
-      } 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[]);
+    .reduce(
+      (previousValue, [key]) => {
+        if (modifiersClassNames[key]) {
+          previousValue.push(modifiersClassNames[key as string]);
+        } 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;
+      },
+      [classNames[UI.Day]] as string[]
+    );
 
   return modifierClassNames;
 }
diff --git a/src/helpers/getComponents.ts b/src/helpers/getComponents.ts
index 3df5dc180..a96143cc2 100644
--- a/src/helpers/getComponents.ts
+++ b/src/helpers/getComponents.ts
@@ -3,7 +3,7 @@ import type { CustomComponents, DayPickerProps } from "../types/index.js";
 
 export function getComponents(
   customComponents: DayPickerProps["components"]
-): Required<CustomComponents> {
+): CustomComponents {
   return {
     ...components,
     ...customComponents
diff --git a/src/helpers/getDataAttributes.tsx b/src/helpers/getDataAttributes.tsx
index 4311c6923..3a051a50d 100644
--- a/src/helpers/getDataAttributes.tsx
+++ b/src/helpers/getDataAttributes.tsx
@@ -6,7 +6,10 @@ export function getDataAttributes(
 ): Record<string, unknown> {
   const dataAttributes: Record<string, unknown> = {
     "data-mode": props.mode ?? undefined,
-    "data-required": "required" in props ? props.required : undefined
+    "data-required": "required" in props ? props.required : undefined,
+    "data-multiple-months":
+      (props.numberOfMonths && props.numberOfMonths > 1) || undefined,
+    "data-week-numbers": props.showWeekNumber || undefined
   };
   Object.entries(props).forEach(([key, val]) => {
     if (key.startsWith("data-")) {
diff --git a/src/helpers/getDefaultClassNames.ts b/src/helpers/getDefaultClassNames.ts
index 49a0ced59..4f0725790 100644
--- a/src/helpers/getDefaultClassNames.ts
+++ b/src/helpers/getDefaultClassNames.ts
@@ -6,7 +6,7 @@ import type { ClassNames } from "../types/index.js";
  *
  * @group Utilities
  */
-export function getDefaultClassNames(): Required<ClassNames> {
+export function getDefaultClassNames(): ClassNames {
   const classNames: Partial<Required<ClassNames>> = {};
 
   for (const key in UI) {
diff --git a/src/helpers/getDropdownMonths.ts b/src/helpers/getMonthOptions.ts
similarity index 58%
rename from src/helpers/getDropdownMonths.ts
rename to src/helpers/getMonthOptions.ts
index 9016a011e..28f2391f1 100644
--- a/src/helpers/getDropdownMonths.ts
+++ b/src/helpers/getMonthOptions.ts
@@ -3,26 +3,23 @@ import type { Locale } from "../lib/dateLib.js";
 import type { DateLib, Formatters } from "../types/index.js";
 
 /** Return the months to show in the dropdown. */
-export function getDropdownMonths(
+export function getMonthOptions(
   displayMonth: Date,
-  calendarStartMonth: Date | undefined,
-  calendarEndMonth: Date | undefined,
+  navStart: Date | undefined,
+  navEnd: Date | undefined,
   formatters: Pick<Formatters, "formatMonthDropdown">,
   locale: Locale | undefined,
   dateLib: DateLib
 ): DropdownOption[] | undefined {
-  if (!calendarStartMonth) return undefined;
-  if (!calendarEndMonth) return undefined;
+  if (!navStart) return undefined;
+  if (!navEnd) return undefined;
 
   const { addMonths, startOfMonth, isBefore, Date } = dateLib;
   const year = displayMonth.getFullYear();
 
-  const navStartMonth = startOfMonth(calendarStartMonth);
-  const navEndMonth = startOfMonth(calendarEndMonth);
-
   const months: number[] = [];
-  let month = navStartMonth;
-  while (months.length < 12 && isBefore(month, addMonths(navEndMonth, 1))) {
+  let month = navStart;
+  while (months.length < 12 && isBefore(month, addMonths(navEnd, 1))) {
     months.push(month.getMonth());
     month = addMonths(month, 1);
   }
@@ -32,10 +29,8 @@ export function getDropdownMonths(
   const options = sortedMonths.map((value) => {
     const label = formatters.formatMonthDropdown(value, locale);
     const disabled =
-      (calendarStartMonth &&
-        new Date(year, value) < startOfMonth(calendarStartMonth)) ||
-      (calendarEndMonth &&
-        new Date(year, value) > startOfMonth(calendarEndMonth)) ||
+      (navStart && new Date(year, value) < startOfMonth(navStart)) ||
+      (navEnd && new Date(year, value) > startOfMonth(navEnd)) ||
       false;
     return { value, label, disabled };
   });
diff --git a/src/helpers/getNavMonth.ts b/src/helpers/getNavMonth.ts
index 56b520447..5c2b280ee 100644
--- a/src/helpers/getNavMonth.ts
+++ b/src/helpers/getNavMonth.ts
@@ -4,18 +4,18 @@ import type { DateLib, DayPickerProps } from "../types/index.js";
 export function getNavMonths(
   props: Pick<
     DayPickerProps,
-    | "startMonth"
+    | "captionLayout"
     | "endMonth"
+    | "startMonth"
     | "today"
-    | "captionLayout"
     // Deprecated:
-    | "fromYear"
-    | "toYear"
     | "fromMonth"
+    | "fromYear"
     | "toMonth"
+    | "toYear"
   >,
   dateLib: DateLib
-): [navStartMonth: Date | undefined, navEndMonth: Date | undefined] {
+): [start: Date | undefined, end: Date | undefined] {
   let { startMonth, endMonth } = props;
 
   const {
diff --git a/src/helpers/getStyleForModifiers.ts b/src/helpers/getStyleForModifiers.ts
index f78140686..dda9d1451 100644
--- a/src/helpers/getStyleForModifiers.ts
+++ b/src/helpers/getStyleForModifiers.ts
@@ -1,12 +1,14 @@
 import type { CSSProperties } from "react";
 
-import type { Modifiers, ModifiersStyles } from "../types/index.js";
+import { UI } from "../UI.js";
+import type { Modifiers, ModifiersStyles, Styles } from "../types/index.js";
 
 export function getStyleForModifiers(
   dayModifiers: Modifiers,
+  styles: Partial<Styles> = {},
   modifiersStyles: Partial<ModifiersStyles> = {}
 ) {
-  let style: CSSProperties = {};
+  let style: CSSProperties = { ...styles?.[UI.Day] };
   Object.entries(dayModifiers)
     .filter(([, active]) => active === true)
     .forEach(([modifier]) => {
@@ -15,8 +17,5 @@ export function getStyleForModifiers(
         ...modifiersStyles?.[modifier]
       };
     });
-  if (Object.keys(style).length === 0) {
-    return undefined;
-  }
   return style;
 }
diff --git a/src/helpers/getDropdownYears.ts b/src/helpers/getYearOptions.ts
similarity index 97%
rename from src/helpers/getDropdownYears.ts
rename to src/helpers/getYearOptions.ts
index 427e3096e..d3357bdb4 100644
--- a/src/helpers/getDropdownYears.ts
+++ b/src/helpers/getYearOptions.ts
@@ -2,7 +2,7 @@ import { DropdownOption } from "../components/Dropdown.js";
 import type { DateLib, Formatters } from "../types/index.js";
 
 /** Return the years to show in the dropdown. */
-export function getDropdownYears(
+export function getYearOptions(
   displayMonth: Date,
   calendarStart: Date | undefined,
   calendarEnd: Date | undefined,
diff --git a/src/selection/useMulti.tsx b/src/selection/useMulti.tsx
index fa55d84b2..d4225e508 100644
--- a/src/selection/useMulti.tsx
+++ b/src/selection/useMulti.tsx
@@ -5,75 +5,61 @@ import type {
   DayPickerProps,
   Modifiers,
   PropsMulti,
-  PropsMultiRequired
+  Selection
 } from "../types/index.js";
 
-export type UseMulti<T> = {
-  handleSelect: (
-    triggerDate: Date,
-    modifiers: Modifiers,
-    e: React.MouseEvent | React.KeyboardEvent
-  ) => Date[] | undefined;
-  isSelected: (date: Date) => boolean;
-} & (T extends { required: true }
-  ? {
-      selected: Date[];
-    }
-  : {
-      selected: Date[] | undefined;
-    });
-
 export function useMulti<T extends DayPickerProps>(
-  props: T extends { mode: "multiple" }
-    ? PropsMulti | PropsMultiRequired
-    : object,
+  props: T,
   dateLib: DateLib
-): UseMulti<T> {
-  const { selected, required, onSelect, mode } = props as PropsMulti;
-  const [dates, setDates] = React.useState<Date[] | undefined>(
-    mode !== "multiple" ? undefined : selected
+): Selection<T> {
+  const {
+    selected: initiallySelected,
+    required,
+    onSelect,
+    mode
+  } = props as PropsMulti;
+  const [selected, setSelected] = React.useState<Date[] | undefined>(
+    initiallySelected
   );
 
   const { isSameDay, Date } = dateLib;
 
   // Update the selected date if the required flag is set.
   React.useEffect(() => {
-    if (mode !== "multiple") return;
-    if (required && dates === undefined) {
-      setDates([new Date()]);
+    if (required && selected === undefined) {
+      setSelected([new Date()]);
     }
-  }, [required, dates, Date, mode]);
+  }, [required, selected, Date, mode]);
 
   // Update the selected date if the selected value from props changes.
   React.useEffect(() => {
-    if (mode !== "multiple") return;
-    setDates(selected);
-  }, [mode, selected]);
+    setSelected(initiallySelected);
+  }, [mode, initiallySelected]);
 
   const isSelected = (date: Date) => {
-    return dates?.some((d) => isSameDay(d, date)) ?? false;
+    return selected?.some((d) => isSameDay(d, date)) ?? false;
   };
 
   const { min, max } = props as PropsMulti;
 
-  const setSelected = (
+  const select = (
     triggerDate: Date,
     modifiers: Modifiers,
     e: React.MouseEvent | React.KeyboardEvent
   ) => {
-    let newDates: Date[] | undefined = [...(dates ?? [])];
+    let newDates: Date[] | undefined = [...(selected ?? [])];
     if (isSelected(triggerDate)) {
-      if (dates?.length === min) {
+      if (selected?.length === min) {
         // Min value reached, do nothing
         return;
       }
-      if (required && dates?.length === 1) {
+      if (required && selected?.length === 1) {
         // Required value already selected do nothing
         return;
       }
-      newDates = dates?.filter((d) => !isSameDay(d, triggerDate));
+      newDates = selected?.filter((d) => !isSameDay(d, triggerDate));
     } else {
-      if (dates?.length === max) {
+      if (selected?.length === max) {
         // Max value reached, reset the selection to date
         newDates = [triggerDate];
       } else {
@@ -82,13 +68,13 @@ export function useMulti<T extends DayPickerProps>(
       }
     }
     onSelect?.(newDates, triggerDate, modifiers, e);
-    setDates(newDates);
+    setSelected(newDates);
     return newDates;
   };
 
   return {
-    selected: dates,
-    handleSelect: setSelected,
+    selected,
+    select,
     isSelected
-  } as UseMulti<T>;
+  } as Selection<T>;
 }
diff --git a/src/selection/useRange.tsx b/src/selection/useRange.tsx
index e7eca835d..b3d7c00ab 100644
--- a/src/selection/useRange.tsx
+++ b/src/selection/useRange.tsx
@@ -1,71 +1,56 @@
 import React from "react";
 
-import {
+import type {
   DateLib,
   DateRange,
   DayPickerProps,
   Modifiers,
   PropsRange,
-  PropsRangeRequired
+  Selection
 } from "../types/index.js";
 import { addToRange, dateMatchModifiers } from "../utils/index.js";
-import { isDateInRange } from "../utils/isDateInRange.js";
-
-export type UseRange<T> = {
-  handleSelect: (
-    triggerDate: Date,
-    modifiers: Modifiers,
-    e: React.MouseEvent | React.KeyboardEvent
-  ) => DateRange | undefined;
-  isSelected: (date: Date) => boolean;
-  isRangeStart: (date: Date) => boolean;
-  isRangeEnd: (date: Date) => boolean;
-  isRangeMiddle: (date: Date) => boolean;
-} & (T extends { required: true }
-  ? {
-      selected: DateRange;
-    }
-  : {
-      selected: DateRange | undefined;
-    });
+import { rangeIncludesDate } from "../utils/rangeIncludesDate.js";
 
 export function useRange<T extends DayPickerProps>(
-  props: T extends { mode: "range" } ? PropsRange | PropsRangeRequired : object,
+  props: T,
   dateLib: DateLib
-): UseRange<T> {
-  const { mode, disabled, selected, required, onSelect } = props as PropsRange;
+): Selection<T> {
+  const {
+    mode,
+    disabled,
+    selected: initiallySelected,
+    required,
+    onSelect
+  } = props as PropsRange;
 
   const { differenceInCalendarDays } = dateLib;
-  const [range, setRange] = React.useState<DateRange | undefined>(
-    mode === "range" ? selected : undefined
+  const [selected, setSelected] = React.useState<DateRange | undefined>(
+    initiallySelected
   );
 
   // Update the selected date if the required flag is set.
   React.useEffect(() => {
-    if (mode !== "range") return;
-    if (required && range === undefined) {
-      setRange({ from: undefined, to: undefined });
+    if (required && selected === undefined) {
+      setSelected({ from: undefined, to: undefined });
     }
-  }, [required, range, mode]);
+  }, [required, selected, mode]);
 
   // Update the selected date if the selected changes.
   React.useEffect(() => {
-    if (mode !== "range") return;
-    if (range === selected) return;
-    setRange(selected);
-  }, [mode, range, selected]);
+    if (selected === initiallySelected) return;
+    setSelected(initiallySelected);
+  }, [mode, selected, initiallySelected]);
 
   const isSelected = (date: Date) =>
-    range && isDateInRange(date, range, dateLib);
+    selected && rangeIncludesDate(selected, date, false, dateLib);
 
-  const setSelected = (
+  const select = (
     triggerDate: Date,
     modifiers: Modifiers,
     e: React.MouseEvent | React.KeyboardEvent
   ) => {
-    if (mode !== "range") return;
     const newRange = triggerDate
-      ? addToRange(triggerDate, range, dateLib)
+      ? addToRange(triggerDate, selected, dateLib)
       : undefined;
     const { min, max } = props as PropsRange;
 
@@ -103,39 +88,15 @@ export function useRange<T extends DayPickerProps>(
       }
     }
 
-    setRange(newRange);
+    setSelected(newRange);
     onSelect?.(newRange, triggerDate, modifiers, e);
 
     return newRange;
   };
 
-  const isRangeStart = (date: Date) => {
-    return (
-      range && range.from && range.to && dateLib.isSameDay(date, range.from)
-    );
-  };
-
-  const isRangeEnd = (date: Date) => {
-    return range && range.to && dateLib.isSameDay(date, range.to);
-  };
-
-  const isRangeMiddle = (date: Date) => {
-    return (
-      range &&
-      range.from &&
-      range.to &&
-      isSelected(date) &&
-      !isRangeStart(date) &&
-      !isRangeEnd(date)
-    );
-  };
-
   return {
-    selected: range,
-    handleSelect: setSelected,
-    isSelected,
-    isRangeStart,
-    isRangeEnd,
-    isRangeMiddle
-  } as UseRange<T>;
+    selected,
+    select,
+    isSelected
+  } as Selection<T>;
 }
diff --git a/src/selection/useSingle.tsx b/src/selection/useSingle.tsx
index 843a7b978..7e2f1d381 100644
--- a/src/selection/useSingle.tsx
+++ b/src/selection/useSingle.tsx
@@ -5,67 +5,60 @@ import type {
   DayPickerProps,
   Modifiers,
   PropsSingle,
-  PropsSingleRequired
+  SelectHandler,
+  SelectedValue,
+  Selection
 } from "../types/index.js";
 
-export type UseSingle<T> = {
-  handleSelect: (
-    triggerDate: Date,
-    modifiers: Modifiers,
-    e: React.MouseEvent | React.KeyboardEvent
-  ) => void;
+export type UseSingle<T extends DayPickerProps> = {
+  select: SelectHandler<T>;
   isSelected: (date: Date) => boolean;
-} & (T extends { required: true }
-  ? {
-      selected: Date;
-    }
-  : {
-      selected: Date | undefined;
-    });
+  selected: SelectedValue<T>;
+};
 
 export function useSingle<T extends DayPickerProps>(
-  props: T extends { mode: "single" }
-    ? PropsSingle | PropsSingleRequired
-    : object,
+  props: DayPickerProps,
   dateLib: DateLib
-): UseSingle<T> {
-  const { selected, required, onSelect, mode } = props as PropsSingle;
+): Selection<T> {
+  const {
+    selected: initiallySelected,
+    required,
+    onSelect,
+    mode
+  } = props as PropsSingle;
 
-  const [date, setDate] = React.useState<Date | undefined>(
-    mode !== "single" ? undefined : selected
+  const [selected, setSelected] = React.useState<Date | undefined>(
+    initiallySelected
   );
 
   const { isSameDay, Date, startOfDay } = dateLib;
   // Update the selected date if the required flag is set.
   React.useEffect(() => {
-    if (mode !== "single") return;
-    if (required && date === undefined) {
-      setDate(startOfDay(new Date()));
+    if (required && selected === undefined) {
+      setSelected(startOfDay(new Date()));
     }
-  }, [required, date, Date, startOfDay, mode]);
+  }, [required, selected, Date, startOfDay, mode]);
 
   // Update the selected date if the `selected` value changes.
   React.useEffect(() => {
-    if (mode !== "single") return;
-    setDate(selected);
-  }, [mode, selected]);
+    setSelected(initiallySelected);
+  }, [mode, initiallySelected]);
 
   const isSelected = (compareDate: Date) => {
-    return date ? isSameDay(date, compareDate) : false;
+    return selected ? isSameDay(selected, compareDate) : false;
   };
 
-  const setSelected = (
+  const select = (
     triggerDate: Date,
     modifiers: Modifiers,
     e: React.MouseEvent | React.KeyboardEvent
   ) => {
-    if (mode !== "single") return;
     let newDate: Date | undefined = triggerDate;
-    if (!required && date && date && isSameDay(triggerDate, date)) {
+    if (!required && selected && selected && isSameDay(triggerDate, selected)) {
       // If the date is the same, clear the selection.
       newDate = undefined;
     }
-    setDate(newDate);
+    setSelected(newDate);
     if (required) {
       onSelect?.(newDate as Date, triggerDate, modifiers, e);
     } else {
@@ -75,8 +68,8 @@ export function useSingle<T extends DayPickerProps>(
   };
 
   return {
-    selected: date,
-    handleSelect: setSelected,
+    selected,
+    select,
     isSelected
-  } as UseSingle<T>;
+  } as Selection<T>;
 }
diff --git a/src/types/index.ts b/src/types/index.ts
index 15827096b..9585b6b69 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -1,3 +1,4 @@
 export * from "./deprecated.js";
 export * from "./shared.js";
 export * from "./props.js";
+export * from "./selection.js";
diff --git a/src/types/props.ts b/src/types/props.ts
index 7c2d8d7eb..d9e932246 100644
--- a/src/types/props.ts
+++ b/src/types/props.ts
@@ -32,7 +32,7 @@ export type DayPickerProps = PropsBase &
     | PropsMultiRequired
     | PropsRange
     | PropsRangeRequired
-    | { mode?: undefined }
+    | { mode?: undefined; required?: undefined }
   );
 
 /**
@@ -391,6 +391,7 @@ export interface PropsBase {
   /**
    * Event handler when a week number is clicked
    *
+   * @private
    * @deprecated Use a custom `WeekNumber` component instead.
    * @see http://daypicker.dev/next/docs/customization#showweeknumber
    */
diff --git a/src/types/selection.ts b/src/types/selection.ts
new file mode 100644
index 000000000..3265d4aa0
--- /dev/null
+++ b/src/types/selection.ts
@@ -0,0 +1,54 @@
+import type { DayPickerProps } from "./props.js";
+import type { DateRange, Modifiers } from "./shared.js";
+
+export type Selection<T extends DayPickerProps> = {
+  /** The selected date(s). */
+  selected: SelectedValue<T> | undefined;
+  /** Set a selection. */
+  select: SelectHandler<T> | undefined;
+  /** Whether the given date is selected. */
+  isSelected: (date: Date) => boolean;
+};
+
+export type SelectedSingle<T extends { required?: boolean }> =
+  T["required"] extends true ? Date : Date | undefined;
+export type SelectedMulti<T extends { required?: boolean }> =
+  T["required"] extends true ? Date[] : Date[] | undefined;
+export type SelectedRange<T extends { required?: boolean }> =
+  T["required"] extends true ? DateRange : DateRange | undefined;
+
+export type SelectedValue<T> = T extends { mode: "single"; required?: boolean }
+  ? SelectedSingle<T>
+  : T extends { mode: "multiple"; required?: boolean }
+    ? SelectedMulti<T>
+    : T extends { mode: "range"; required?: boolean }
+      ? SelectedRange<T>
+      : undefined;
+
+export type SelectHandlerSingle<T extends DayPickerProps> = (
+  triggerDate: Date,
+  modifiers: Modifiers,
+  e: React.MouseEvent | React.KeyboardEvent
+) => T["required"] extends true ? Date : Date | undefined;
+
+export type SelectHandlerMulti<T extends DayPickerProps> = (
+  triggerDate: Date,
+  modifiers: Modifiers,
+  e: React.MouseEvent | React.KeyboardEvent
+) => T["required"] extends true ? Date[] : Date[] | undefined;
+
+export type SelectHandlerRange<T extends DayPickerProps> = (
+  triggerDate: Date,
+  modifiers: Modifiers,
+  e: React.MouseEvent | React.KeyboardEvent
+) => T["required"] extends true ? DateRange : DateRange | undefined;
+
+export type SelectHandler<T extends DayPickerProps> = T extends {
+  mode: "single";
+}
+  ? SelectHandlerSingle<T>
+  : T extends { mode: "multiple" }
+    ? SelectHandlerMulti<T>
+    : T extends { mode: "range" }
+      ? SelectHandlerRange<T>
+      : undefined;
diff --git a/src/types/shared.ts b/src/types/shared.ts
index ddf8bd1a8..a24e22582 100644
--- a/src/types/shared.ts
+++ b/src/types/shared.ts
@@ -91,6 +91,7 @@ export type CustomComponents = {
   WeekNumberHeader: typeof components.WeekNumberHeader;
 };
 
+/** @private */
 export type DateLib = typeof dateLib;
 
 /** Represent a map of formatters used to render localized content. */
@@ -148,8 +149,6 @@ export type Labels = {
 /**
  * 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;
@@ -165,6 +164,7 @@ export type Labels = {
  *
  * // 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) };
  *
@@ -202,89 +202,77 @@ export type Matcher =
   | DateInterval
   | DayOfWeek;
 
-/**
- * A matcher to match a day falling after the specified date, with the date not
- * included.
- */
+/** 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.
- */
+/** 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.
+ * An interval of dates. Differently from {@link DateRange}, the range ends here
+ * 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.
+ * A range of dates. The range can be open. Differently from
+ * {@link DateInterval}, the range ends 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).
+ * Match dates 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>;
+export type DayOfWeek = { dayOfWeek: number | number[] };
 
 /**
  * The event handler triggered when interacting with a day.
  *
- * @template EventType - The event type that triggered the day event.
+ * @template EventType - The event type that triggered the event (e.g.
+ *   `React.MouseEvent`, `React.KeyboardEvent`, etc.).
+ * @param date - The date that has triggered the event.
+ * @param modifiers - The modifiers belonging to the date.
+ * @param e - The DOM event that triggered the 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;
 
-/** Maps user interface elements, selection states, and flags to a CSS style. */
-export type Styles = {
-  [key in UI | SelectionState | DayFlag]: CSSProperties | undefined;
-};
-
-/** Defines the class names for various UI components and states. */
+/**
+ * The CSS classnames to use for the {@link UI } elements, the
+ * {@link SelectionState} and the {@link DayFlag}.
+ */
 export type ClassNames = {
   [key in UI | SelectionState | DayFlag]: 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 CSS styles to use for the {@link UI } elements, the {@link SelectionState}
+ * and the {@link DayFlag}.
+ */
+export type Styles = {
+  [key in UI | SelectionState | DayFlag]: CSSProperties | undefined;
+};
 
 /** 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>;
+export type Modifiers = Record<string, boolean>;
 
 /** The style to apply to each day element matching a modifier. */
-export type ModifiersStyles = Record<string, CSSProperties> &
-  Partial<Record<DayFlag, CSSProperties>>;
+export type ModifiersStyles = Record<string, CSSProperties>;
 
 /** The classnames to assign to each day element matching a modifier. */
-export type ModifiersClassNames = Record<string, string> &
-  Partial<Record<DayFlag & SelectionState, string>>;
+export type ModifiersClassNames = Record<string, string>;
 
 /**
  * The props that have been deprecated since version 9.0.0.
  *
+ * @private
  * @since 9.0.0
  * @see https://daypicker.dev/next/upgrading
  */
diff --git a/src/useCalendar.ts b/src/useCalendar.ts
index a2eb47a65..35aaee2de 100644
--- a/src/useCalendar.ts
+++ b/src/useCalendar.ts
@@ -17,14 +17,11 @@ import type { DayPickerProps } from "./types/props.js";
 import type { DateLib } from "./types/shared.js";
 
 /**
- * The hook to get and handle the calendar state.
+ * Return the calendar object to work with the calendar in custom components.
  *
  * @see https://daypicker.dev/next/guides/custom-components
  */
-export interface UseCalendar {
-  today: Date;
-  /** All the unique dates displayed to the calendar. */
-  dates: Date[];
+export interface Calendar {
   /**
    * All the days displayed in the calendar. As opposite from
    * {@link CalendarContext.dates}, it may return duplicated dates when shown
@@ -35,16 +32,6 @@ export interface UseCalendar {
   weeks: CalendarWeek[];
   /** The months displayed in the calendar. */
   months: CalendarMonth[];
-  /**
-   * 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 `numberOfMonths` is greater
-   * than `1`, it is the last of the displayed months.
-   */
-  lastMonth: Date;
 
   /** The next month to display. */
   nextMonth: Date | undefined;
@@ -55,22 +42,15 @@ export interface UseCalendar {
    * The month where the navigation starts. `undefined` if the calendar can be
    * navigated indefinitely to the past.
    */
-  navigationStartMonth: Date | undefined;
+  navStart: Date | undefined;
   /**
    * The month where the navigation ends. `undefined` if the calendar can be
    * navigated indefinitely to the past.
    */
-  navigationEndMonth: Date | undefined;
-
-  /** Set the first month displayed in the calendar. */
-  setFirstMonth: (date: Date) => void;
+  navEnd: Date | undefined;
 
   /** Navigate to the specified month. Will fire the `onMonthChange` callback. */
   goToMonth: (month: Date) => void;
-  /** Navigate to the next month. */
-  goToNextMonth: () => void;
-  /** Navigate to the previous month. */
-  goToPreviousMonth: () => void;
   /**
    * Navigate to the specified date. If the second parameter (refDate) is
    * provided and the date is before the refDate, then the month is set to one
@@ -81,34 +61,11 @@ export interface UseCalendar {
    *   month is set to one month before the date.
    */
   goToDay: (day: CalendarDay) => void;
-  /** Whether the given date is included in the displayed months. */
-  isDayDisplayed: (day: CalendarDay) => boolean;
 }
 
 /** @private */
-export function useCalendar(
-  props: Pick<
-    DayPickerProps,
-    | "fromYear"
-    | "toYear"
-    | "startMonth"
-    | "endMonth"
-    | "month"
-    | "defaultMonth"
-    | "today"
-    | "numberOfMonths"
-    | "disableNavigation"
-    | "onMonthChange"
-    | "ISOWeek"
-  >,
-  dateLib: DateLib
-) {
-  const today = dateLib.startOfDay(props.today ?? new dateLib.Date());
-
-  const [navigationStartMonth, navigationEndMonth] = getNavMonths(
-    props,
-    dateLib
-  );
+export function useCalendar(props: DayPickerProps, dateLib: DateLib): Calendar {
+  const [navStart, navEnd] = getNavMonths(props, dateLib);
 
   const { startOfMonth } = dateLib;
 
@@ -120,105 +77,67 @@ export function useCalendar(
     props.month ? startOfMonth(props.month) : undefined
   );
 
-  /** An array of the months displayed in the calendar. */
-  const displayMonths = getDisplayMonths(
-    firstMonth,
-    navigationEndMonth,
-    props,
-    dateLib
-  );
-
-  /** The last month displayed in the calendar. */
-  const lastMonth = displayMonths[displayMonths.length - 1];
+  /** The months displayed in the calendar. */
+  const displayMonths = getDisplayMonths(firstMonth, navEnd, props, dateLib);
 
-  /** An array of the dates displayed in the calendar. */
+  /** The dates displayed in the calendar. */
   const dates = getDates(displayMonths, props.endMonth, props, dateLib);
 
-  /** An array of the Months displayed in the calendar. */
+  /** The Months displayed in the calendar. */
   const months = getMonths(displayMonths, dates, props, dateLib);
 
-  /** An array of the Weeks displayed in the calendar. */
+  /** The Weeks displayed in the calendar. */
   const weeks = getWeeks(months);
 
-  /** An array of the Days displayed in the calendar. */
+  /** The Days displayed in the calendar. */
   const days = getDays(months);
 
-  const previousMonth = getPreviousMonth(
-    firstMonth,
-    navigationStartMonth,
-    props,
-    dateLib
-  );
-  const nextMonth = getNextMonth(
-    firstMonth,
-    navigationEndMonth,
-    props,
-    dateLib
-  );
+  const previousMonth = getPreviousMonth(firstMonth, navStart, props, dateLib);
+  const nextMonth = getNextMonth(firstMonth, navEnd, props, dateLib);
 
   const { disableNavigation, onMonthChange } = props;
 
-  function isDayDisplayed(day: CalendarDay) {
-    return weeks.some((week: CalendarWeek) => {
-      return week.days.some((d) => {
-        return d.isEqualTo(day);
-      });
-    });
-  }
+  const isDayInCalendar = (day: CalendarDay) =>
+    weeks.some((week: CalendarWeek) => week.days.some((d) => d.isEqualTo(day)));
 
-  function goToMonth(date: Date) {
+  const goToMonth = (date: Date) => {
     if (disableNavigation) {
       return;
     }
     let newMonth = startOfMonth(date);
     // if month is before start, use the first month instead
-    if (navigationStartMonth && newMonth < startOfMonth(navigationStartMonth)) {
-      newMonth = startOfMonth(navigationStartMonth);
+    if (navStart && newMonth < startOfMonth(navStart)) {
+      newMonth = startOfMonth(navStart);
     }
     // if month is after endMonth, use the last month instead
-    if (navigationEndMonth && newMonth > startOfMonth(navigationEndMonth)) {
-      newMonth = startOfMonth(navigationEndMonth);
+    if (navEnd && newMonth > startOfMonth(navEnd)) {
+      newMonth = startOfMonth(navEnd);
     }
     setFirstMonth(newMonth);
     onMonthChange?.(newMonth);
-  }
+  };
 
-  function goToDay(day: CalendarDay) {
-    if (isDayDisplayed(day)) {
+  const goToDay = (day: CalendarDay) => {
+    // is this check necessary?
+    if (isDayInCalendar(day)) {
       return;
     }
     goToMonth(day.date);
-  }
-
-  function goToNextMonth() {
-    return nextMonth ? goToMonth(nextMonth) : undefined;
-  }
-  function goToPreviousMonth() {
-    return previousMonth ? goToMonth(previousMonth) : undefined;
-  }
+  };
 
-  const calendar: UseCalendar = {
-    dates,
+  const calendar = {
     months,
     weeks,
     days,
-    today,
 
-    navigationStartMonth: navigationStartMonth,
-    navigationEndMonth: navigationEndMonth,
+    navStart,
+    navEnd,
 
-    firstMonth: firstMonth,
-    lastMonth,
     previousMonth,
     nextMonth,
 
-    setFirstMonth,
-
-    isDayDisplayed,
     goToMonth,
-    goToDay,
-    goToNextMonth,
-    goToPreviousMonth
+    goToDay
   };
 
   return calendar;
diff --git a/src/useDayPicker.ts b/src/useDayPicker.ts
index 2013c5299..0602fadd2 100644
--- a/src/useDayPicker.ts
+++ b/src/useDayPicker.ts
@@ -1,31 +1,37 @@
 import { createContext, useContext } from "react";
 
+import { CalendarDay } from "./classes/CalendarDay.js";
+import { CalendarMonth } from "./classes/CalendarMonth.js";
 import type { DayPickerProps } from "./types/props.js";
-import type { UseCalendar } from "./useCalendar.js";
-import type { UseModifiers } from "./useModifiers.js";
-import type {
-  UseMulti,
-  UseRange,
-  UseSelection,
-  UseSingle
-} from "./useSelection.js";
-
-export type {
-  UseCalendar,
-  UseModifiers,
-  UseSelection,
-  UseMulti,
-  UseRange,
-  UseSingle
-};
+import type { SelectedValue, SelectHandler } from "./types/selection.js";
+import { Modifiers } from "./types/shared.js";
 
 // Create a context with a default value
 export const dayPickerContext = createContext<
-  (UseCalendar & UseModifiers & UseSelection<DayPickerProps>) | undefined
+  DayPickerContext<DayPickerProps> | undefined
 >(undefined);
 
+export type DayPickerContext<T extends DayPickerProps> = {
+  /** The months displayed in the calendar. */
+  months: CalendarMonth[];
+  /** The next month to display. */
+  nextMonth: Date | undefined;
+  /** The previous month to display. */
+  previousMonth: Date | undefined;
+  /** Navigate to the specified month. Will fire the `onMonthChange` callback. */
+  goToMonth: (month: Date) => void;
+  /** Returns the modifiers for the given day. */
+  getModifiers: (day: CalendarDay) => Modifiers;
+  /** The selected date(s). */
+  selected: SelectedValue<T> | undefined;
+  /** Set a selection. */
+  select: SelectHandler<T> | undefined;
+  /** Whether the given date is selected. */
+  isSelected: ((date: Date) => boolean) | undefined;
+};
+
 /**
- * Return API to work with `<DayPicker />` inside custom components.
+ * Return the context to work with `<DayPicker />` inside custom components.
  *
  * @group Hooks
  * @see https://daypicker.dev/next/guides/custom-components
@@ -35,5 +41,5 @@ export function useDayPicker<T extends DayPickerProps>(props?: T) {
   if (context === undefined) {
     throw new Error("useDayPicker() must be used within a custom component.");
   }
-  return context as UseCalendar & UseModifiers & UseSelection<T>;
+  return context as DayPickerContext<T>;
 }
diff --git a/src/useFocus.ts b/src/useFocus.ts
index e4b283efd..aacced65a 100644
--- a/src/useFocus.ts
+++ b/src/useFocus.ts
@@ -8,11 +8,9 @@ import type {
   MoveFocusDir,
   DateLib,
   DayPickerProps,
-  Mode
+  Modifiers
 } from "./types/index.js";
-import { UseCalendar } from "./useCalendar.js";
-import { UseModifiers } from "./useModifiers.js";
-import { UseSelection } from "./useSelection.js";
+import { Calendar } from "./useCalendar.js";
 
 export type UseFocus = {
   /** The date that is currently focused. */
@@ -24,41 +22,28 @@ export type UseFocus = {
   /** Focus the given day. */
   setFocused: (day: CalendarDay | undefined) => void;
 
-  /** Set the last focused day. */
-  setLastFocused: (day: CalendarDay | undefined) => void;
-
   /** Blur the focused day. */
   blur: () => void;
 
+  /** Move the current focus to the next day according to the given direction. */
   moveFocus: (moveBy: MoveFocusBy, moveDir: MoveFocusDir) => void;
 };
 
 /** @private */
-export function useFocus(
-  props: Pick<
-    DayPickerProps,
-    | "autoFocus"
-    | "disabled"
-    | "hidden"
-    | "modifiers"
-    | "numberOfMonths"
-    | "locale"
-    | "ISOWeek"
-    | "weekStartsOn"
-  >,
-  calendar: UseCalendar,
-  modifiers: UseModifiers,
-  selection: UseSelection<{ mode: Mode }>,
+export function useFocus<T extends DayPickerProps>(
+  props: T,
+  calendar: Calendar,
+  getModifiers: (day: CalendarDay) => Modifiers,
+  isSelected: (date: Date) => boolean,
   dateLib: DateLib
 ): UseFocus {
-  const { getModifiers } = modifiers;
   const { autoFocus } = props;
   const [lastFocused, setLastFocused] = useState<CalendarDay | undefined>();
 
   const focusTarget = calculateFocusTarget(
-    calendar,
+    calendar.days,
     getModifiers,
-    selection.isSelected,
+    isSelected || (() => false),
     lastFocused
   );
   const [focusedDay, setFocused] = useState<CalendarDay | undefined>(
@@ -76,8 +61,8 @@ export function useFocus(
       moveBy,
       moveDir,
       focusedDay,
-      calendar.navigationStartMonth,
-      calendar.navigationEndMonth,
+      calendar.navStart,
+      calendar.navEnd,
       props,
       dateLib
     );
@@ -95,7 +80,6 @@ export function useFocus(
     isFocusTarget,
     setFocused,
     focused: focusedDay,
-    setLastFocused,
     blur,
     moveFocus
   };
diff --git a/src/useModifiers.tsx b/src/useGetModifiers.tsx
similarity index 68%
rename from src/useModifiers.tsx
rename to src/useGetModifiers.tsx
index e5a28bff0..beadda21b 100644
--- a/src/useModifiers.tsx
+++ b/src/useGetModifiers.tsx
@@ -1,43 +1,22 @@
 import { DayFlag, SelectionState } from "./UI.js";
 import { CalendarDay } from "./classes/index.js";
-import type {
-  CustomModifiers,
-  DateLib,
-  DayFlags,
-  DayPickerProps,
-  Modifiers,
-  SelectionStates
-} from "./types/index.js";
-import type { UseCalendar } from "./useCalendar.js";
+import type { DateLib, DayPickerProps, Modifiers } from "./types/index.js";
 import { dateMatchModifiers } from "./utils/dateMatchModifiers.js";
 
-export type UseModifiers = {
-  /** 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;
-};
-
-/** @private */
-export function useModifiers(
-  props: Pick<
-    DayPickerProps,
-    "disabled" | "hidden" | "modifiers" | "showOutsideDays" | "today"
-  >,
-  calendar: UseCalendar,
+/**
+ * Return a function to get the modifiers for a given day.
+ *
+ * @private
+ */
+export function useGetModifiers(
+  days: CalendarDay[],
+  props: DayPickerProps,
   dateLib: DateLib
-): UseModifiers {
-  // const single = useSingle();
-  // const multi = useMulti();
-  // const range = useRange();
-
+) {
   const { disabled, hidden, modifiers, showOutsideDays, today } = props;
 
   const { isSameDay, isSameMonth, Date } = dateLib;
+
   const internal: Record<DayFlag, CalendarDay[]> = {
     [DayFlag.focused]: [],
     [DayFlag.outside]: [],
@@ -55,7 +34,7 @@ export function useModifiers(
     [SelectionState.selected]: []
   };
 
-  for (const day of calendar.days) {
+  for (const day of days) {
     const { date, displayMonth } = day;
 
     const isOutside = Boolean(displayMonth && !isSameMonth(date, displayMonth));
@@ -92,22 +71,22 @@ export function useModifiers(
     }
   }
 
-  const getModifiers = (day: CalendarDay) => {
+  return (day: CalendarDay) => {
     // Initialize all the modifiers to false
-    const dayFlags: DayFlags = {
+    const dayFlags: Record<DayFlag, boolean> = {
       [DayFlag.focused]: false,
       [DayFlag.disabled]: false,
       [DayFlag.hidden]: false,
       [DayFlag.outside]: false,
       [DayFlag.today]: false
     };
-    const selectionStates: SelectionStates = {
+    const selectionStates: Record<SelectionState, boolean> = {
       [SelectionState.range_end]: false,
       [SelectionState.range_middle]: false,
       [SelectionState.range_start]: false,
       [SelectionState.selected]: false
     };
-    const customModifiers: CustomModifiers = {};
+    const customModifiers: Modifiers = {};
 
     // Find the modifiers for the given day
     for (const name in internal) {
@@ -127,13 +106,6 @@ export function useModifiers(
       ...dayFlags,
       // custom modifiers should override all the previous ones
       ...customModifiers
-    };
-  };
-
-  return {
-    dayFlags: internal,
-    customModifiers: custom,
-    selectionStates: selection,
-    getModifiers
+    } as Modifiers;
   };
 }
diff --git a/src/useSelection.ts b/src/useSelection.ts
index 5ada49714..d403cdfbc 100644
--- a/src/useSelection.ts
+++ b/src/useSelection.ts
@@ -1,58 +1,25 @@
-import { dateLib as defaultDateLib } from "./lib/dateLib.js";
-import { type UseMulti, useMulti } from "./selection/useMulti.js";
-import { type UseRange, useRange } from "./selection/useRange.js";
-import { type UseSingle, useSingle } from "./selection/useSingle.js";
-import type {
-  DateLib,
-  DayPickerProps,
-  Mode,
-  PropsMulti,
-  PropsMultiRequired,
-  PropsRange,
-  PropsRangeRequired,
-  PropsSingle,
-  PropsSingleRequired
-} from "./types/index.js";
-
-export type { UseMulti, UseRange, UseSingle };
+import { useMulti } from "./selection/useMulti.js";
+import { useRange } from "./selection/useRange.js";
+import { useSingle } from "./selection/useSingle.js";
+import type { DateLib, DayPickerProps } from "./types/index.js";
+import { Selection } from "./types/selection.js";
 
 export function useSelection<T extends DayPickerProps>(
   props: T,
-  dateLib?: DateLib
-): UseSelection<T> {
-  const lib = { ...defaultDateLib, ...dateLib };
-  const single = useSingle<T>(props as PropsSingle | PropsSingleRequired, lib);
-  const multi = useMulti<T>(props as PropsMulti | PropsMultiRequired, lib);
-  const range = useRange<T>(props as PropsRange | PropsRangeRequired, lib);
+  dateLib: DateLib
+): Selection<T> | undefined {
+  const single = useSingle(props, dateLib);
+  const multi = useMulti(props, dateLib);
+  const range = useRange(props, dateLib);
 
   switch (props.mode) {
     case "single":
-      // @ts-expect-error The type of `single` is `UseSingle<T>`.
       return single;
     case "multiple":
-      // @ts-expect-error The type of `multi` is `UseMulti<T>`.
       return multi;
     case "range":
-      // @ts-expect-error The type of `range` is `UseRange<T>`.
       return range;
     default:
-      // @ts-expect-error The type of the return value correct
-      return {
-        handleSelect: () => undefined,
-        isSelected: () => false,
-        selected: undefined
-      };
+      return undefined;
   }
 }
-
-export type UseSelection<T> = T extends { mode: Mode }
-  ? {
-      single: UseSingle<T>;
-      multiple: UseMulti<T>;
-      range: UseRange<T>;
-    }[T["mode"]]
-  : {
-      handleSelect: () => undefined;
-      isSelected: () => false;
-      selected: undefined;
-    };
diff --git a/src/utils/dateMatchModifiers.ts b/src/utils/dateMatchModifiers.ts
index 1c702fac5..f2a19d18f 100644
--- a/src/utils/dateMatchModifiers.ts
+++ b/src/utils/dateMatchModifiers.ts
@@ -1,22 +1,15 @@
 import type { DateLib, Matcher } from "../types/index.js";
 
-import { isDateInRange } from "./isDateInRange.js";
+import { rangeIncludesDate } from "./rangeIncludesDate.js";
 import {
   isDateAfterType,
   isDateBeforeType,
   isDateInterval,
   isDateRange,
+  isDatesArray,
   isDayOfWeekType
 } from "./typeguards.js";
 
-/** Returns true if `value` is an array of valid dates. */
-export function isDatesArray(
-  value: unknown,
-  dateLib: DateLib
-): value is Date[] {
-  return Array.isArray(value) && value.every(dateLib.isDate);
-}
-
 /**
  * Returns whether a day matches against at least one of the given
  * {@link Matcher}.
@@ -54,9 +47,12 @@ export function dateMatchModifiers(
       return matcher.includes(date);
     }
     if (isDateRange(matcher)) {
-      return isDateInRange(date, matcher, dateLib);
+      return rangeIncludesDate(matcher, date, false, dateLib);
     }
     if (isDayOfWeekType(matcher)) {
+      if (!Array.isArray(matcher.dayOfWeek)) {
+        return matcher.dayOfWeek === date.getDay();
+      }
       return matcher.dayOfWeek.includes(date.getDay());
     }
     if (isDateInterval(matcher)) {
diff --git a/src/utils/index.ts b/src/utils/index.ts
index 5af61224c..0c563cbe0 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -1,4 +1,4 @@
 export * from "./addToRange.js";
 export * from "./dateMatchModifiers.js";
-export * from "./isDateInRange.js";
+export * from "./rangeIncludesDate.js";
 export * from "./typeguards.js";
diff --git a/src/utils/isDateInRange.ts b/src/utils/rangeIncludesDate.ts
similarity index 57%
rename from src/utils/isDateInRange.ts
rename to src/utils/rangeIncludesDate.ts
index f7eb0ec0f..9fd7bb74a 100644
--- a/src/utils/isDateInRange.ts
+++ b/src/utils/rangeIncludesDate.ts
@@ -4,11 +4,14 @@ import type { DateRange, DateLib } from "../types/index.js";
 /**
  * Determines whether a given date is inside a specified date range.
  *
+ * @since 9.0.0
  * @group Utilities
  */
-export function isDateInRange(
-  date: Date,
+export function rangeIncludesDate(
   range: DateRange,
+  date: Date,
+  /** If `true`, the ends of the range are excluded. */
+  excludeEnds = false,
   /** @ignore */
   dateLib: DateLib = defaultDateLib
 ): boolean {
@@ -20,15 +23,22 @@ export function isDateInRange(
       [from, to] = [to, from];
     }
     const isInRange =
-      differenceInCalendarDays(date, from) >= 0 &&
-      differenceInCalendarDays(to, date) >= 0;
+      differenceInCalendarDays(date, from) >= (excludeEnds ? 1 : 0) &&
+      differenceInCalendarDays(to, date) >= (excludeEnds ? 1 : 0);
     return isInRange;
   }
-  if (to) {
+  if (!excludeEnds && to) {
     return isSameDay(to, date);
   }
-  if (from) {
+  if (!excludeEnds && from) {
     return isSameDay(from, date);
   }
   return false;
 }
+
+/**
+ * @private
+ * @deprecated Use {@link rangeIncludesDate} instead.
+ */
+export const isDateInRange = (range: DateRange, date: Date) =>
+  rangeIncludesDate(range, date, false, defaultDateLib);
diff --git a/src/utils/typeguards.ts b/src/utils/typeguards.ts
index 13f7b34f4..d3807fbda 100644
--- a/src/utils/typeguards.ts
+++ b/src/utils/typeguards.ts
@@ -2,16 +2,13 @@ import type {
   DateAfter,
   DateBefore,
   DateInterval,
+  DateLib,
   DateRange,
-  DayOfWeek,
-  DayPickerProps,
-  PropsMulti,
-  PropsRange,
-  PropsSingle
+  DayOfWeek
 } from "../types/index.js";
 
 /**
- * Returns true if `matcher` is of type `DateInterval`.
+ * Returns true if `matcher` is of type {@link DateInterval}.
  *
  * @group Utilities
  */
@@ -25,7 +22,7 @@ export function isDateInterval(matcher: unknown): matcher is DateInterval {
 }
 
 /**
- * Returns true if `value` is a `DateRange` type.
+ * Returns true if `value` is a {@link DateRange} type.
  *
  * @group Utilities
  */
@@ -34,7 +31,7 @@ export function isDateRange(value: unknown): value is DateRange {
 }
 
 /**
- * Returns true if `value` is of type `DateAfter`.
+ * Returns true if `value` is of type {@link DateAfter}.
  *
  * @group Utilities
  */
@@ -43,7 +40,7 @@ export function isDateAfterType(value: unknown): value is DateAfter {
 }
 
 /**
- * Returns true if `value` is of type `DateBefore`.
+ * Returns true if `value` is of type {@link DateBefore}.
  *
  * @group Utilities
  */
@@ -52,52 +49,7 @@ export function isDateBeforeType(value: unknown): value is DateBefore {
 }
 
 /**
- * Returns true if the props are for a single selection mode.
- *
- * @group Utilities
- */
-export function isSingle(
-  props: DayPickerProps
-): props is DayPickerProps & PropsSingle {
-  return props.mode === "single";
-}
-
-/**
- * @deprecated This function has been renamed Use `isSingle` instead.
- * @protected
- */
-export const isDayPickerSingle = isSingle;
-
-/**
- * Returns true if the props are for a multiple selection mode.
- *
- * @group Utilities
- */
-export function isMulti(
-  props: DayPickerProps
-): props is DayPickerProps & PropsMulti {
-  return props.mode === "multiple";
-}
-
-/**
- * @deprecated This function has been renamed Use `isMulti` instead.
- * @protected
- */
-export const isDayPickerMultiple = isMulti;
-
-/**
- * Returns true if the props are for a range selection mode.
- *
- * @group Utilities
- */
-export function isRange(
-  props: DayPickerProps
-): props is DayPickerProps & PropsRange {
-  return props.mode === "range";
-}
-
-/**
- * Returns true if `value` is a `DayOfWeek` type.
+ * Returns true if `value` is a {@link DayOfWeek} type.
  *
  * @group Utilities
  */
@@ -105,8 +57,10 @@ export function isDayOfWeekType(value: unknown): value is DayOfWeek {
   return Boolean(value && typeof value === "object" && "dayOfWeek" in value);
 }
 
-/**
- * @deprecated This function has been renamed Use `isRange` instead.
- * @protected
- */
-export const isDayPickerRange = isRange;
+/** Returns true if `value` is an array of valid dates. */
+export function isDatesArray(
+  value: unknown,
+  dateLib: DateLib
+): value is Date[] {
+  return Array.isArray(value) && value.every(dateLib.isDate);
+}
diff --git a/website/docs/docs/accessibility.mdx b/website/docs/docs/accessibility.mdx
index 5bf35d4f4..953153c90 100644
--- a/website/docs/docs/accessibility.mdx
+++ b/website/docs/docs/accessibility.mdx
@@ -2,7 +2,7 @@
 sidebar_position: 7
 ---
 
-# Accessibility
+# Accessible Date Pickers
 
 DayPicker is designed to adhere to the [ARIA Authoring Practices Guide](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/examples/datepicker-dialog/) for date pickers. This includes features such as keyboard navigation, focus management, and labeling.
 
@@ -17,11 +17,11 @@ Depending on your design, you may need to add new accessibility features. For in
 - Ensure that your date picker is fully navigable using only the keyboard.
 - Use clear indicators for focus states to assist users who rely on keyboard navigation.
 - Ensure sufficient color contrast between text and background colors.
-- Consider providing instructions for how to use the date picker for **first-time users** or those unfamiliar with the interface.
+- Consider providing instructions for how to use the date picker for first-time users or those unfamiliar with the interface.
 
-### Announcing the Selected Date {#footer}
+## Announcing the Selected Date {#footer}
 
-An accessible date picker with a live region that announces the selected date using the `footer` prop.
+Example of an accessible date picker with a live region that announces the selected date using the `footer` prop.
 
 ```tsx title="./AccessibleDatePicker.tsx"
 import { format } from "date-fns";
@@ -73,22 +73,22 @@ DayPicker manages focus automatically when the user interacts with the calendar.
 
 DayPicker supports keyboard navigation to make it easier for users to navigate the calendar. The following keys are supported:
 
-| Keys                         | Function                            |
-| ---------------------------- | ----------------------------------- |
-| <kbd>Arrow Top</kbd>         | Move focus to the previous week     |
-| <kbd>Arrow Right</kbd>       | Move focus to the next day          |
-| <kbd>Arrow Bottom</kbd>      | Move focus to the next week         |
-| <kbd>Arrow Left</kbd>        | Move focus to the previous day      |
-| <kbd>Page Up</kbd>           | Move focus to the previous month    |
-| <kbd>Page Down</kbd>         | Move focus to the next month        |
-| <kbd>Shift + Page Up</kbd>   | Move focus to the previous year     |
-| <kbd>Shift + Page Down</kbd> | Move focus to the next year         |
-| <kbd>Home</kbd>              | Move focus to the start of the week |
-| <kbd>End</kbd>               | Move focus to the end of the week   |
-| <kbd>Enter/Space</kbd>       | Select the focused day              |
-
-## Reporting Issues With Accessibility
-
-Accessibility is an evolving field. If you find any issues with DayPicker, please [open an issue](https://github.com/gpbl/react-day-picker/issues/new/choose). Your feedback helps improve our library's accessibility.
+| Keys                         | Function                             |
+| ---------------------------- | ------------------------------------ |
+| <kbd>Arrow Top</kbd>         | Move focus to the previous week.     |
+| <kbd>Arrow Right</kbd>       | Move focus to the next da.y          |
+| <kbd>Arrow Bottom</kbd>      | Move focus to the next week.         |
+| <kbd>Arrow Left</kbd>        | Move focus to the previous day.      |
+| <kbd>Page Up</kbd>           | Move focus to the previous month.    |
+| <kbd>Page Down</kbd>         | Move focus to the next month.        |
+| <kbd>Shift + Page Up</kbd>   | Move focus to the previous year.     |
+| <kbd>Shift + Page Down</kbd> | Move focus to the next year.         |
+| <kbd>Home</kbd>              | Move focus to the start of the week. |
+| <kbd>End</kbd>               | Move focus to the end of the week.   |
+| <kbd>Enter/Space</kbd>       | Select the focused day.              |
+
+## Getting Help With Accessibility
+
+Accessibility is an evolving field. If you find any accessibility issues with DayPicker, please [open an issue](https://github.com/gpbl/react-day-picker/issues/new/choose). Your feedback helps improve our library's accessibility.
 
 Check out the [current accessibility issues](https://github.com/gpbl/react-day-picker/issues?q=is%3Aopen+label%3Aaccessibility+sort%3Aupdated-desc).
diff --git a/website/docs/guides/custom-selections.mdx b/website/docs/guides/custom-selections.mdx
index dc42f8f95..9f31c84d0 100644
--- a/website/docs/guides/custom-selections.mdx
+++ b/website/docs/guides/custom-selections.mdx
@@ -10,7 +10,14 @@ This documentation is still a work in progress. If you have any questions, ask t
 
 :::
 
-If the built-in selection modes are not enough for your app’s requirements, you can control the selection behavior using `onDayClick`.
+If the built-in [selection modes](../docs/selection-modes.mdx) does not satisfy for your app’s requirements, you can control the selection behavior using the [`onDayClick`](../api/interfaces/PropsBase.md#ondayclick) and [`modifiers`](../api/interfaces/PropsBase.md#modifiers) props:
+
+| Prop         | Type                                                        | Description                               |
+| ------------ | ----------------------------------------------------------- | ----------------------------------------- |
+| `onDayClick` | [`DayEventHandler`](../api/type-aliases/DayEventHandler.md) | Callback when a day is clicked.           |
+| `modifiers`  | [`Modifiers`](../api/type-aliases/Modifiers.md)             | An object with the modifiers of the days. |
+
+This prop is a function that receives the clicked day and the modifiers of the day as arguments.
 
 ## Examples
 
@@ -21,10 +28,10 @@ The following example shows how to select the whole week when a day is clicked.
 Note the use of the `startOfWeek` and `endOfWeek` functions from [date-fns](https://date-fns.org) and how the `selectedWeek` state is passed to the `modifiers` prop.
 
 ```tsx
-import { useState } from "react";
+import React, { useState } from "react";
 
-import { endOfWeek, isSameWeek, startOfWeek } from "date-fns";
-import { DateRange, DayPicker } from "react-day-picker";
+import { endOfWeek, startOfWeek } from "date-fns";
+import { DateRange, DayPicker, isDateInRange } from "react-day-picker";
 
 /** Select the whole week when the day is clicked. */
 export function CustomWeek() {
@@ -33,12 +40,19 @@ export function CustomWeek() {
   return (
     <DayPicker
       showWeekNumber
+      showOutsideDays
       modifiers={{
-        selected: selectedWeek
+        selected: selectedWeek,
+        range_start: selectedWeek?.from,
+        range_end: selectedWeek?.to,
+        range_middle: (date: Date) =>
+          selectedWeek
+            ? isDateInRange(date, selectedWeek, { excludeEnds: true })
+            : false
       }}
       onDayClick={(day, modifiers) => {
         if (modifiers.selected) {
-          setSelectedWeek(undefined); // clear the selection if the week is already selected
+          setSelectedWeek(undefined); // clear the selection if the day is already selected
           return;
         }
         setSelectedWeek({
@@ -46,23 +60,10 @@ export function CustomWeek() {
           to: endOfWeek(day)
         });
       }}
-      onWeekNumberClick={(weekNumber, dates) => {
-        if (selectedWeek?.from && isSameWeek(dates[0], selectedWeek.from)) {
-          setSelectedWeek(undefined); // clear the selection if the week is already selected
-          return;
-        }
-        setSelectedWeek({
-          from: startOfWeek(dates[0]),
-          to: endOfWeek(dates[dates.length - 1])
-        });
-      }}
       footer={
-        selectedWeek && (
-          <p>
-            Week from {selectedWeek.from.toLocaleDateString()} to
-            {selectedWeek.to.toLocaleDateString()}
-          </p>
-        )
+        selectedWeek &&
+        `Week from ${selectedWeek?.from?.toLocaleDateString()} to
+            ${selectedWeek?.to?.toLocaleDateString()}`
       }
     />
   );
diff --git a/website/docs/start.mdx b/website/docs/start.mdx
index 08131f941..35fc22eec 100644
--- a/website/docs/start.mdx
+++ b/website/docs/start.mdx
@@ -14,10 +14,10 @@ npm install react-day-picker@next
 
 ## Basic Usage
 
-To create a simple date picker calendar,
+To start adding a simple date picker to your app:
 
-1. Import the component and its default style from `react-day-picker`.
-2. Choose a [selection mode](./docs/selection-modes) using the `mode` prop.
+1. Import the component and the style from `react-day-picker`.
+2. Choose a [selection mode](./docs/selection-modes) with the `mode` prop.
 3. Assign the `selected` and `onSelect` props to manage the selected date.
 
 ```tsx
@@ -48,20 +48,22 @@ export function MyDatePicker() {
 
 ## Learn More
 
-{/* - [DayPicker Anatomy](./docs/anatomy) - Understand the parts composing a DayPicker component. */}
-{/* - [DayPicker Playground](./playground) - Play with the props to try the different customization options. */}
+- [DayPicker Anatomy](./docs/anatomy.mdx) - Understand the parts composing a DayPicker component.
+- [DayPicker Playground](/playground) - Play with the props to try the different customization options.
 
 ### Using DayPicker
 
-- [Styling](./docs/styling) - Change the style to make DayPicker match your app's look and feel.
-- [Customization](./docs/customization) - Explore the options available to customize the calendar and the months navigation.
-- [Selection Modes](./docs/selection-modes) - Enable users to select days with single, multiple or range selections.
-- [Localization](./docs/localization) - Configure DayPicker to display the calendar in different languages and date formats.
-- [Accessible Date Pickers](./docs/accessibility) - Make your date picker accessible to all users.
+- [Styling](./docs/styling.mdx) - Change the style to make DayPicker match your app's look and feel.
+- [Customization](./docs/customization.mdx) - Explore the options available to customize the calendar.
+- [Months Navigation](./docs/navigation.mdx) - Change how user can navigate between months and years.
+- [Selection Modes](./docs/selection-modes.mdx) - Enable users to select days with single, multiple or range selections.
+- [Localization](./docs/localization.mdx) - Configure DayPicker to display the calendar in different languages and date formats.
+- [Translating DayPicker](./docs/translation.mdx) - Translate the labels and messages of the calendar.
+- [Accessible Date Pickers](./docs/accessibility.mdx) - Make your date picker accessible to all users.
 
 ### Guides
 
 - [Input Fields](./guides/input-fields) - Learn how to use DayPicker with input fields and form libraries.
 - [Custom Selections](./guides/custom-selections) - Create custom selection rules.
-- [Custom Components](./guides/custom-components) - Use custom components to create a fully customized date picker.
 - [Custom Modifiers](./guides/custom-modifiers) - Create custom modifiers to highlight specific days in the calendar.
+- [Custom Components](./guides/custom-components) - Use custom components to create a fully customized date picker.
diff --git a/website/docs/upgrading.mdx b/website/docs/upgrading.mdx
index 52355931c..041498636 100644
--- a/website/docs/upgrading.mdx
+++ b/website/docs/upgrading.mdx
@@ -23,7 +23,7 @@ npm install react-day-picker@latest
 
 ### 2. Update your custom styles
 
-The CSS classes have been updated and simplified. If you are using custom styles, you will need to update them. See the [styles guide](./guides/styles.mdx) for more information.
+The CSS classes have been updated and simplified. If you are using custom styles, you will need to update them. See the [styling docs](./docs/styling.mdx) for more information.
 
 ### 3. Remove use of `fromDate`, `toDate`
 
diff --git a/website/src/css/docusaurus-reset.css b/website/src/css/docusaurus-reset.css
index 98768cef7..641b3e691 100644
--- a/website/src/css/docusaurus-reset.css
+++ b/website/src/css/docusaurus-reset.css
@@ -142,11 +142,13 @@
 .docusaurus-reset table.rdp-month_grid td {
   border: revert;
   padding: revert;
+  font-size: 1rem;
 }
 
 .docusaurus-reset table th {
   background-color: revert;
   color: revert;
+  font-size: 0.875rem;
 }
 
 .docusaurus-reset table td {
diff --git a/website/src/pages/playground.tsx b/website/src/pages/playground.tsx
index 26f8874f8..fe360b630 100644
--- a/website/src/pages/playground.tsx
+++ b/website/src/pages/playground.tsx
@@ -6,9 +6,7 @@ import {
   DateRange,
   DayPicker,
   DayPickerProps,
-  Mode,
-  isDateRange,
-  isSingle
+  isDateRange
 } from "react-day-picker";
 import { DayPicker as DayPickerUtc } from "react-day-picker/utc";
 
@@ -232,7 +230,106 @@ export default function Playground() {
               )}
             </div>
           </fieldset>
-
+          <fieldset>
+            <legend>Selection</legend>
+            <div className={styles.fields}>
+              <label>
+                Selection mode:
+                <select
+                  name="mode"
+                  onChange={(e) => {
+                    const mode = e.target.value || undefined;
+                    const newProps = {
+                      ...props,
+                      mode
+                    };
+                    setSelected(undefined);
+                    // @ts-expect-error Not working well with the union type
+                    setProps(newProps);
+                  }}
+                  value={props.mode}
+                >
+                  <option></option>
+                  <option value="single">single</option>
+                  <option value="multiple">multiple</option>
+                  <option value="range">range</option>
+                </select>
+              </label>
+              {props.mode && (
+                <label>
+                  <input
+                    type="checkbox"
+                    checked={props.required}
+                    name="required"
+                    onChange={(e) => {
+                      setSelected(undefined);
+                      // @ts-expect-error abc
+                      setProps({ ...props, required: e.target.checked });
+                    }}
+                  />
+                  Required
+                </label>
+              )}
+              {props.mode === "range" || props.mode === "multiple" ? (
+                <label>
+                  Min Selection:
+                  <input
+                    type="number"
+                    min={0}
+                    max={12}
+                    size={4}
+                    name="min"
+                    onChange={(e) => {
+                      setProps({
+                        ...props,
+                        min: Number(e.target.value)
+                      });
+                    }}
+                  />
+                </label>
+              ) : null}
+              {props.mode === "range" || props.mode === "multiple" ? (
+                <label>
+                  Max Selection:
+                  <input
+                    type="number"
+                    min={0}
+                    max={12}
+                    size={4}
+                    name="max"
+                    onChange={(e) => {
+                      setProps({
+                        ...props,
+                        max: Number(e.target.value)
+                      });
+                    }}
+                  />
+                </label>
+              ) : null}
+              {props.mode === "range" && (
+                <>
+                  <label>
+                    Range Background:
+                    <input
+                      value={backgroundAccentColor ?? ""}
+                      type="color"
+                      onChange={(e) =>
+                        setBackgroundAccountColor(e.target.value)
+                      }
+                    />
+                  </label>
+                  <label>
+                    Range Foreground:
+                    <input
+                      value={rangeMiddleColor ?? ""}
+                      type="color"
+                      onChange={(e) => setrangeMiddleColor(e.target.value)}
+                    />
+                  </label>
+                </>
+              )}
+            </div>
+          </fieldset>
           <fieldset>
             <legend>Localization</legend>
             <div className={styles.fields}>
@@ -337,106 +434,6 @@ export default function Playground() {
               </label>
             </div>
           </fieldset>
-          <fieldset>
-            <legend>Selection</legend>
-            <div className={styles.fields}>
-              <label>
-                Selection mode:
-                <select
-                  name="mode"
-                  onChange={(e) => {
-                    const mode = e.target.value || undefined;
-                    const newProps = {
-                      ...props,
-                      mode
-                    };
-                    setSelected(undefined);
-                    // @ts-expect-error Not working well with the union type
-                    setProps(newProps);
-                  }}
-                  value={props.mode}
-                >
-                  <option></option>
-                  <option value="single">single</option>
-                  <option value="multiple">multiple</option>
-                  <option value="range">range</option>
-                </select>
-              </label>
-              {props.mode && (
-                <label>
-                  <input
-                    type="checkbox"
-                    checked={props.required}
-                    name="required"
-                    onChange={(e) => {
-                      setSelected(undefined);
-                      // @ts-expect-error abc
-                      setProps({ ...props, required: e.target.checked });
-                    }}
-                  />
-                  Required
-                </label>
-              )}
-              {props.mode === "range" || props.mode === "multiple" ? (
-                <label>
-                  Min Selection:
-                  <input
-                    type="number"
-                    min={0}
-                    max={12}
-                    size={4}
-                    name="min"
-                    onChange={(e) => {
-                      setProps({
-                        ...props,
-                        min: Number(e.target.value)
-                      });
-                    }}
-                  />
-                </label>
-              ) : null}
-              {props.mode === "range" || props.mode === "multiple" ? (
-                <label>
-                  Max Selection:
-                  <input
-                    type="number"
-                    min={0}
-                    max={12}
-                    size={4}
-                    name="max"
-                    onChange={(e) => {
-                      setProps({
-                        ...props,
-                        max: Number(e.target.value)
-                      });
-                    }}
-                  />
-                </label>
-              ) : null}
-              {props.mode === "range" && (
-                <>
-                  <label>
-                    Range Background:
-                    <input
-                      value={backgroundAccentColor ?? ""}
-                      type="color"
-                      onChange={(e) =>
-                        setBackgroundAccountColor(e.target.value)
-                      }
-                    />
-                  </label>
-                  <label>
-                    Range Foreground:
-                    <input
-                      value={rangeMiddleColor ?? ""}
-                      type="color"
-                      onChange={(e) => setrangeMiddleColor(e.target.value)}
-                    />
-                  </label>
-                </>
-              )}
-            </div>
-          </fieldset>
         </form>
         <div className={styles.browserWindow}>
           <BrowserWindow url="">
diff --git a/website/typedoc.mjs b/website/typedoc.mjs
index 19db19482..9bdb02167 100644
--- a/website/typedoc.mjs
+++ b/website/typedoc.mjs
@@ -43,6 +43,7 @@ const options = {
 
   typeDeclarationFormat: "list",
   parametersFormat: "table",
+  expandParameters: true,
   propertiesFormat: "list",
   propertyMembersFormat: "list",
   enumMembersFormat: "table",


Test patch:
diff --git a/examples/__snapshots__/Range.test.tsx.snap b/examples/__snapshots__/Range.test.tsx.snap
index f2a07d10f..29b599052 100644
--- a/examples/__snapshots__/Range.test.tsx.snap
+++ b/examples/__snapshots__/Range.test.tsx.snap
@@ -4,7 +4,6 @@ exports[`should match the snapshot 1`] = `
 <div>
   <div
     class="rdp-root"
-    data-interactive="true"
     data-mode="range"
     id="test"
   >
@@ -643,7 +642,6 @@ exports[`when a day in the range is clicked when the day is clicked again when a
 <div>
   <div
     class="rdp-root"
-    data-interactive="true"
     data-mode="range"
     id="test"
   >
diff --git a/examples/__snapshots__/StylingCssModules.test.tsx.snap b/examples/__snapshots__/StylingCssModules.test.tsx.snap
index e98b1bd32..cd9fc9d25 100644
--- a/examples/__snapshots__/StylingCssModules.test.tsx.snap
+++ b/examples/__snapshots__/StylingCssModules.test.tsx.snap
@@ -6,7 +6,6 @@ exports[`should match the snapshot 1`] = `
 >
   <div
     class="rdp-root"
-    data-interactive="true"
     data-mode="single"
   >
     <div
diff --git a/src/helpers/getDropdownMonths.test.ts b/src/helpers/getMonthOptions.test.ts
similarity index 93%
rename from src/helpers/getDropdownMonths.test.ts
rename to src/helpers/getMonthOptions.test.ts
index 38a6de718..a5ecc771a 100644
--- a/src/helpers/getDropdownMonths.test.ts
+++ b/src/helpers/getMonthOptions.test.ts
@@ -2,8 +2,8 @@ import { type Locale, format } from "date-fns";
 import { enUS as locale } from "date-fns/locale";
 import { dateLib } from "react-day-picker";
 
-import { getDropdownMonths } from "./getDropdownMonths";
 import { getFormatters } from "./getFormatters";
+import { getMonthOptions } from "./getMonthOptions";
 
 test("return correct dropdown options", () => {
   const displayMonth = new Date(2022, 0, 1); // January 2022
@@ -13,7 +13,7 @@ test("return correct dropdown options", () => {
     formatMonthDropdown: (month: number, locale?: Locale) =>
       format(new Date(2022, month), "MMMM", { locale })
   });
-  const result = getDropdownMonths(
+  const result = getMonthOptions(
     displayMonth,
     startMonth,
     endMonth,
diff --git a/src/helpers/getStyleForModifiers.test.ts b/src/helpers/getStyleForModifiers.test.ts
index 0284e05d5..444f5ff2f 100644
--- a/src/helpers/getStyleForModifiers.test.ts
+++ b/src/helpers/getStyleForModifiers.test.ts
@@ -31,7 +31,7 @@ test("applies modifier styles to the base style", () => {
     ...modifiersStyles.selected
   };
 
-  const style = getStyleForModifiers(dayModifiers, modifiersStyles);
+  const style = getStyleForModifiers(dayModifiers, {}, modifiersStyles);
 
   expect(style).toEqual(expectedStyle);
 });
@@ -46,7 +46,7 @@ test("ignores modifiers that are not active", () => {
     disabled: { opacity: 0.5 }
   };
 
-  const style = getStyleForModifiers(dayModifiers, modifiersStyles);
+  const style = getStyleForModifiers(dayModifiers, {}, modifiersStyles);
 
   expect(style).toEqual({ opacity: 0.5 }); // should not have applied the disabled style
 });
@@ -66,7 +66,7 @@ test("combines multiple active modifier styles", () => {
     ...modifiersStyles.highlighted
   };
 
-  const style = getStyleForModifiers(dayModifiers, modifiersStyles);
+  const style = getStyleForModifiers(dayModifiers, {}, modifiersStyles);
 
   expect(style).toEqual(expectedStyle);
 });
@@ -86,7 +86,7 @@ test("applies the most recent modifier style when there are conflicts", () => {
     color: "green" // from 'highlighted', overriding 'selected'
   };
 
-  const style = getStyleForModifiers(dayModifiers, modifiersStyles);
+  const style = getStyleForModifiers(dayModifiers, {}, modifiersStyles);
 
   expect(style).toEqual(expectedStyle);
 });
diff --git a/src/helpers/getDropdownYears.test.ts b/src/helpers/getYearOptions.test.ts
similarity index 88%
rename from src/helpers/getDropdownYears.test.ts
rename to src/helpers/getYearOptions.test.ts
index a97888b66..79d2879a3 100644
--- a/src/helpers/getDropdownYears.test.ts
+++ b/src/helpers/getYearOptions.test.ts
@@ -1,21 +1,21 @@
 import { dateLib } from "react-day-picker";
 
-import { getDropdownYears } from "./getDropdownYears";
 import { getFormatters } from "./getFormatters";
+import { getYearOptions } from "./getYearOptions";
 
 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(
+  const result1 = getYearOptions(
     displayMonth,
     undefined,
     new Date(2022, 11, 31),
     formatters,
     dateLib
   );
-  const result2 = getDropdownYears(
+  const result2 = getYearOptions(
     displayMonth,
     new Date(2022, 0, 1),
     undefined,
@@ -35,7 +35,7 @@ test("return correct dropdown options", () => {
     formatYearDropdown: (year: number) => `${year}`
   });
 
-  const result = getDropdownYears(
+  const result = getYearOptions(
     displayMonth,
     startMonth,
     endMonth,
diff --git a/src/utils/isDateInRange.test.ts b/src/utils/rangeIncludesDate.test.ts
similarity index 75%
rename from src/utils/isDateInRange.test.ts
rename to src/utils/rangeIncludesDate.test.ts
index db33f14b2..69b4df81d 100644
--- a/src/utils/isDateInRange.test.ts
+++ b/src/utils/rangeIncludesDate.test.ts
@@ -2,20 +2,20 @@ import { addDays } from "date-fns/addDays";
 
 import { DateRange } from "../types";
 
-import { isDateInRange } from "./isDateInRange";
+import { rangeIncludesDate } from "./rangeIncludesDate";
 
 const date = new Date();
 
 describe('when range is missing the "from" date', () => {
   const range: DateRange = { from: undefined };
-  const result = isDateInRange(date, range);
+  const result = rangeIncludesDate(range, date);
   test("should return false", () => {
     expect(result).toBe(false);
   });
 });
 
 describe('when range is missing the "to" date', () => {
-  const result = isDateInRange(date, { from: date, to: undefined });
+  const result = rangeIncludesDate({ from: date, to: undefined }, date);
   test("should return true", () => {
     expect(result).toBe(true);
   });
@@ -23,7 +23,7 @@ describe('when range is missing the "to" date', () => {
 
 describe("when the range dates are the same as date", () => {
   const range: DateRange = { from: date, to: date };
-  const result = isDateInRange(date, range);
+  const result = rangeIncludesDate(range, date);
   test("should return true", () => {
     expect(result).toBe(true);
   });
@@ -31,7 +31,7 @@ describe("when the range dates are the same as date", () => {
 
 describe("when the range dates are the same but not as date", () => {
   const range: DateRange = { from: date, to: date };
-  const result = isDateInRange(addDays(date, 1), range);
+  const result = rangeIncludesDate(range, addDays(date, 1));
   test("should return false", () => {
     expect(result).toBe(false);
   });
@@ -39,7 +39,7 @@ describe("when the range dates are the same but not as date", () => {
 
 describe("when the range is inverted", () => {
   const range: DateRange = { from: addDays(date, 1), to: date };
-  const result = isDateInRange(date, range);
+  const result = rangeIncludesDate(range, date);
   test("should return true", () => {
     expect(result).toBe(true);
   });
diff --git a/src/utils/typeguards.test.ts b/src/utils/typeguards.test.ts
new file mode 100644
index 000000000..63ec85d2d
--- /dev/null
+++ b/src/utils/typeguards.test.ts
@@ -0,0 +1,83 @@
+import { dateLib } from "../lib/dateLib.js";
+import type {
+  DateInterval,
+  DateRange,
+  DateAfter,
+  DateBefore,
+  DayOfWeek
+} from "../types/index.js";
+
+import {
+  isDateInterval,
+  isDateRange,
+  isDateAfterType,
+  isDateBeforeType,
+  isDayOfWeekType,
+  isDatesArray
+} from "./typeguards.js";
+
+test("isDateInterval return true for valid DateInterval", () => {
+  const validInterval: DateInterval = { before: new Date(), after: new Date() };
+  expect(isDateInterval(validInterval)).toBe(true);
+});
+
+test("isDateInterval return false for invalid DateInterval", () => {
+  expect(isDateInterval({})).toBe(false);
+  expect(isDateInterval(null)).toBe(false);
+  expect(isDateInterval(undefined)).toBe(false);
+});
+
+test("isDateRange return true for valid DateRange", () => {
+  const validRange: DateRange = { from: new Date() };
+  expect(isDateRange(validRange)).toBe(true);
+});
+
+test("isDateRange return false for invalid DateRange", () => {
+  expect(isDateRange({})).toBe(false);
+  expect(isDateRange(null)).toBe(false);
+  expect(isDateRange(undefined)).toBe(false);
+});
+
+test("isDateAfterType return true for valid DateAfter", () => {
+  const validAfter: DateAfter = { after: new Date() };
+  expect(isDateAfterType(validAfter)).toBe(true);
+});
+
+test("isDateAfterType return false for invalid DateAfter", () => {
+  expect(isDateAfterType({})).toBe(false);
+  expect(isDateAfterType(null)).toBe(false);
+  expect(isDateAfterType(undefined)).toBe(false);
+});
+
+test("isDateBeforeType return true for valid DateBefore", () => {
+  const validBefore: DateBefore = { before: new Date() };
+  expect(isDateBeforeType(validBefore)).toBe(true);
+});
+
+test("isDateBeforeType return false for invalid DateBefore", () => {
+  expect(isDateBeforeType({})).toBe(false);
+  expect(isDateBeforeType(null)).toBe(false);
+  expect(isDateBeforeType(undefined)).toBe(false);
+});
+
+test("isDayOfWeekType return true for valid DayOfWeek", () => {
+  const validDayOfWeek: DayOfWeek = { dayOfWeek: [1] };
+  expect(isDayOfWeekType(validDayOfWeek)).toBe(true);
+});
+
+test("isDayOfWeekType return false for invalid DayOfWeek", () => {
+  expect(isDayOfWeekType({})).toBe(false);
+  expect(isDayOfWeekType(null)).toBe(false);
+  expect(isDayOfWeekType(undefined)).toBe(false);
+});
+
+test("isDatesArray return true for valid array of dates", () => {
+  const validDatesArray: Date[] = [new Date(), new Date()];
+  expect(isDatesArray(validDatesArray, dateLib)).toBe(true);
+});
+
+test("isDatesArray return false for invalid array of dates", () => {
+  expect(isDatesArray([{}, {}], dateLib)).toBe(false);
+  expect(isDatesArray(null, dateLib)).toBe(false);
+  expect(isDatesArray(undefined, dateLib)).toBe(false);
+});