Repository: gpbl/react-day-picker (2025-08-04T11:53:35Z)

Issue:
Custom Day component breaks keyboard navigation between days.
Thanks for reporting a bug for DayPicker. Please provide here a brief description of the issue you’ve encountered. 


## To reproduce

Please reproduce the bug by forking [this codesandbox](https://codesandbox.io/p/sandbox/7tdhgv). Add your code there and share the URL. Otherwise copy-paste it below.

If you’re unable to easily reproduce this bug, consider opening a [support thread](https://github.com/gpbl/react-day-picker/discussions) to discuss it further.

```tsx
import React from "react";
import { DayButtonProps, DayPicker } from "react-day-picker";

const SelectedDateContext = React.createContext<{
  selected?: Date;
  setSelected?: React.Dispatch<React.SetStateAction<Date | undefined>>;
}>({});

function DayButton(props: DayButtonProps) {
  const { day, modifiers, ...buttonProps } = props;

  const { setSelected } = React.useContext(SelectedDateContext);
  return (
    <button
      {...buttonProps}
      onClick={() => setSelected?.(undefined)}
      onDoubleClick={() => setSelected?.(day.date)}
    />
  );
}

export function CustomDayButton() {
  const [selected, setSelected] = React.useState<Date>();

  return (
    <SelectedDateContext.Provider value={{ selected, setSelected }}>
      <DayPicker
        mode="single"
        selected={selected}
        onSelect={setSelected}
        components={{
          DayButton,
        }}
        footer={selected?.toDateString() || "Double click to select a date"}
      />
    </SelectedDateContext.Provider>
  );
}

export default CustomDayButton;
```

CodeSandbox: https://codesandbox.io/p/sandbox/7tdhgv

## Actual Behavior

Keyboard navigation is broken when using custom components

## Expected Behavior

Keyboard navigation should work, i should be able to forward the attributes to the day buttons enabling it

## Screenshots

If relevant, include screenshots or GIFs to illustrate the bug.



Discussion:
Thanks for reporting @gilb3rt0 !

@gpbl, I see the [Custom Components docs](https://daypicker.dev/guides/custom-components#intercepting-click-events) is giving this example that has issues.

The issue is happening because the custom component is missing the following logic from the core [DayButton component](https://github.com/gpbl/react-day-picker/blob/main/src/components/DayButton.tsx):
```tsx
  const ref = React.useRef<HTMLButtonElement>(null);
  React.useEffect(() => {
    if (modifiers.focused) ref.current?.focus();
  }, [modifiers.focused]);
```

I see two ways of giving better directions:

1 - Recommend checking the source code of the core components to reuse their implementation.

2 - Recommend reusing the core components and composing on top of them.
E.g.:
```tsx
import { DayButton, DayButtonProps } from "react-day-picker";

function CustomDayButton(props: DayButtonProps) {
  // ... custom logic
  return <DayButton {...props} />
}
```

Both approaches could be recommended.
I think 2 is better, it's more future-proof because it keeps in sync with future changes to the core components.

@gpbl Let me know what you think.
@rodgobbi good points, I'm updating the docs in [#2815](https://github.com/gpbl/react-day-picker/pull/2815).
Hello @gpbl. Quick question. Why is `day` part of the `DayButtonProps` if it is never used for the button? Currently if I do something like this (because I want to have a custom logic for the class names):

```ts
function CustomDayButton({ modifiers, ...rest }: DayButtonProps) {
	return (
		<button
			{...rest}
			className={modifiers && customClassesLogic}
		></button>
	)
}
```

In the HTML there is a rendered `day="[object Object]"` prop. But if I destructure it too without passing it, like I see above:
```ts
const { day, modifiers, ...buttonProps } = props;
```
I get an error `'day' is defined but never used` and I have to do some hacks to fix it.
> I get an error `'day' is defined but never used` and I have to do some hacks to fix it.


@todor0v are you referring to the eslint error? Then the `ignoreRestSiblings` setting could be useful in such cases..

https://eslint.org/docs/latest/rules/no-unused-vars#ignorerestsiblings



Patch:
diff --git a/examples/CustomDayButton.tsx b/examples/CustomDayButton.tsx
index 320d687f7..710143efe 100644
--- a/examples/CustomDayButton.tsx
+++ b/examples/CustomDayButton.tsx
@@ -1,19 +1,21 @@
 import React from "react";
 
-import { type DayButtonProps, DayPicker } from "react-day-picker";
+import { DayButton, type DayButtonProps, DayPicker } from "react-day-picker";
 
 const SelectedDateContext = React.createContext<{
   selected?: Date;
   setSelected?: React.Dispatch<React.SetStateAction<Date | undefined>>;
 }>({});
 
-function DayButton(props: DayButtonProps) {
+function DayButtonWithContext(props: DayButtonProps) {
   const { day, modifiers, ...buttonProps } = props;
 
   const { setSelected } = React.use(SelectedDateContext);
   return (
-    <button
+    <DayButton
       {...buttonProps}
+      day={day}
+      modifiers={modifiers}
       onClick={() => setSelected?.(undefined)}
       onDoubleClick={() => setSelected?.(day.date)}
     />
@@ -25,14 +27,15 @@ export function CustomDayButton() {
 
   return (
     <SelectedDateContext.Provider value={{ selected, setSelected }}>
+      <p>Double click to select a date or single click to clear selection</p>
       <DayPicker
         mode="single"
         selected={selected}
         onSelect={setSelected}
         components={{
-          DayButton,
+          DayButton: DayButtonWithContext,
         }}
-        footer={selected?.toDateString() || "Double click to select a date"}
+        footer={selected?.toDateString()}
       />
     </SelectedDateContext.Provider>
   );
diff --git a/react-day-picker.code-workspace b/react-day-picker.code-workspace
index d119e9078..c00b3c959 100644
--- a/react-day-picker.code-workspace
+++ b/react-day-picker.code-workspace
@@ -43,5 +43,6 @@
       "versioned_docs": true
     },
     "jest.runMode": "on-demand",
+    "editor.defaultFormatter": "biomejs.biome",
   }
 }
diff --git a/website/docs/guides/custom-components.mdx b/website/docs/guides/custom-components.mdx
index ee4ba419e..736636ee6 100644
--- a/website/docs/guides/custom-components.mdx
+++ b/website/docs/guides/custom-components.mdx
@@ -28,7 +28,9 @@ Custom components allow you to extend DayPicker beyond just using [formatters](.
 
 ## Implementing a Custom Component
 
-Pass the components you want to customize to the `components` prop. See the [list of custom components](#list-of-custom-components) below.
+- Pass the components you want to customize to the `components` prop (see the [list of custom components](#list-of-custom-components) below).
+- Consider reusing the core components and composing your customizations on top of them.
+- Use a custom React Context to share state between your custom components and your application.
 
 ```tsx
 <DayPicker
@@ -107,32 +109,36 @@ The DayPicker context provides the current state of the calendar, including the
 
 ### Intercepting Click Events
 
-For example, you can use a custom [DayButton](../api/functions/DayButton.md) to select days with a double-click event.
+For example, you can use a custom [DayButton](../api/functions/DayButton.md) to select days with a double-click event. This approach uses a custom React context to share the selected date state between the custom `DayButton` and the main component.
 
 ```tsx title="./MyDatePicker.tsx"
 import React from "react";
 
-import { DayButtonProps, DayPicker } from "react-day-picker";
+import { DayButton, type DayButtonProps, DayPicker } from "react-day-picker";
 
+// Create a context to share the selected date state between the custom DayButton and the main component.
 const SelectedDateContext = React.createContext<{
   selected?: Date;
   setSelected?: React.Dispatch<React.SetStateAction<Date | undefined>>;
 }>({});
 
-function DayButton(props: DayButtonProps) {
+// Custom DayButton that uses the context to set the selected date on double-click and clear it on single click.
+function DayButtonWithContext(props: DayButtonProps) {
   const { day, modifiers, ...buttonProps } = props;
 
-  const { setSelected } = React.useContext(SelectedDateContext);
+  const { setSelected } = React.use(SelectedDateContext);
   return (
-    <button
+    <DayButton
       {...buttonProps}
+      day={day}
+      modifiers={modifiers}
       onClick={() => setSelected?.(undefined)}
       onDoubleClick={() => setSelected?.(day.date)}
     />
   );
 }
 
-export function CustomDayButton() {
+export function MyDatePicker() {
   const [selected, setSelected] = React.useState<Date>();
 
   return (
@@ -142,9 +148,8 @@ export function CustomDayButton() {
         selected={selected}
         onSelect={setSelected}
         components={{
-          DayButton,
+          DayButton: DayButtonWithContext,
         }}
-        footer={selected?.toDateString() || "Double click to select a date"}
       />
     </SelectedDateContext.Provider>
   );


Test patch:
diff --git a/examples/CustomDayButton.test.tsx b/examples/CustomDayButton.test.tsx
index 3ecf60964..142a6b43c 100644
--- a/examples/CustomDayButton.test.tsx
+++ b/examples/CustomDayButton.test.tsx
@@ -21,5 +21,5 @@ test("update the footer when a day is double clicked", () => {
 
 test("update the footer when a day is single clicked", () => {
   fireEvent.click(dateButton(startOfMonth(today)));
-  expect(screen.getByText("Double click to select a date")).toBeInTheDocument();
+  expect(screen.getByText(/Double click to select a date/)).toBeInTheDocument();
 });