Input Fields
Implementing DayPicker together with an input field requires complex interaction and design considerations, which we do not bake into the DayPicker component.
As a developer, you will need to handle the integration of DayPicker with an input field in your application. This involves managing user interactions, syncing the selected date between the calendar and the input field, and ensuring accessibility and usability.
See the examples below as a starting point for implementing a date picker with input fields.
Browsers implement native date pickers that provides a simple, built-in method for users to select a date. However, the appearance and format of the date picker can vary between different browsers and may not offer the level of customization you require.
Examples
These examples showcase different approaches for integrating DayPicker with input fields, such as using an inline calendar or a date picker dialog.
Input with Inline Calendar
This example demonstrates how to integrate an input field with an inline DayPicker calendar. The input field allows users to type a date, and the calendar updates the selected date based on the input value.
See also the full source code and the unit tests for this example.
import { useId, useState } from "react";
import { format, isValid, parse } from "date-fns";
import { DayPicker } from "react-day-picker";
/** Render an input field bound to a DayPicker calendar. */
export function Input() {
const inputId = useId();
// Hold the month in state to control the calendar when the input changes
const [month, setMonth] = useState(new Date());
// Hold the selected date in state
const [selectedDate, setSelectedDate] = useState<Date | undefined>(undefined);
// Hold the input value in state
const [inputValue, setInputValue] = useState("");
const handleDayPickerSelect = (date: Date | undefined) => {
if (!date) {
setInputValue("");
setSelectedDate(undefined);
} else {
setSelectedDate(date);
setMonth(date);
setInputValue(format(date, "MM/dd/yyyy"));
}
};
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setInputValue(e.target.value); // keep the input value in sync
const parsedDate = parse(e.target.value, "MM/dd/yyyy", new Date());
if (isValid(parsedDate)) {
setSelectedDate(parsedDate);
setMonth(parsedDate);
} else {
setSelectedDate(undefined);
}
};
return (
<div>
<label htmlFor={inputId}>
<strong>Date: </strong>
</label>
<input
style={{ fontSize: "inherit" }}
id={inputId}
type="text"
value={inputValue}
placeholder="MM/dd/yyyy"
onChange={handleInputChange}
/>
<DayPicker
month={month}
onMonthChange={setMonth}
mode="single"
selected={selectedDate}
onSelect={handleDayPickerSelect}
footer={
<p aria-live="assertive" aria-atomic="true">
Selected: {selectedDate?.toDateString()}
</p>
}
/>
</div>
);
}
Input with Date Picker Dialog
Implementing the date picker as a dialog requires careful consideration of accessibility. You can refer to the W3C ARIA Authoring Practices for guidance on implementing an accessible date picker.
In this example, we use the native HTML <dialog>
element, which provides a built-in way to create a modal dialog. You can replace the native <dialog>
element with a custom dialog component or a modal library that fits your application's design and accessibility requirements.
import { useEffect, useId, useRef, useState } from "react";
import { format, isValid, parse } from "date-fns";
import { DayPicker } from "react-day-picker";
export function Dialog() {
const dialogRef = useRef<HTMLDialogElement>(null);
const dialogId = useId();
const headerId = useId();
// Hold the month in state to control the calendar when the input changes
const [month, setMonth] = useState(new Date());
// Hold the selected date in state
const [selectedDate, setSelectedDate] = useState<Date | undefined>(undefined);
// Hold the input value in state
const [inputValue, setInputValue] = useState("");
// Hold the dialog visibility in state
const [isDialogOpen, setIsDialogOpen] = useState(false);
// Function to toggle the dialog visibility
const toggleDialog = () => setIsDialogOpen(!isDialogOpen);
// Hook to handle the body scroll behavior and focus trapping.
useEffect(() => {
const handleBodyScroll = (isOpen: boolean) => {
document.body.style.overflow = isOpen ? "hidden" : "";
};
if (!dialogRef.current) return;
if (isDialogOpen) {
handleBodyScroll(true);
dialogRef.current.showModal();
} else {
handleBodyScroll(false);
dialogRef.current.close();
}
return () => {
handleBodyScroll(false);
};
}, [isDialogOpen]);
/**
* Function to handle the DayPicker select event: update the input value and
* the selected date, and set the month.
*/
const handleDayPickerSelect = (date: Date) => {
if (!date) {
setInputValue("");
setSelectedDate(undefined);
} else {
setSelectedDate(date);
setInputValue(format(date, "MM/dd/yyyy"));
}
dialogRef.current?.close();
};
/**
* Handle the input change event: parse the input value to a date, update the
* selected date and set the month.
*/
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setInputValue(e.target.value); // keep the input value in sync
const parsedDate = parse(e.target.value, "MM/dd/yyyy", new Date());
if (isValid(parsedDate)) {
setSelectedDate(parsedDate);
setMonth(parsedDate);
} else {
setSelectedDate(undefined);
}
};
return (
<div>
<label htmlFor="date-input">
<strong>Pick a Date: </strong>
</label>
<input
style={{ fontSize: "inherit" }}
id="date-input"
type="text"
value={inputValue}
placeholder={"MM/dd/yyyy"}
onChange={handleInputChange}
/>{" "}
<button
style={{ fontSize: "inherit" }}
onClick={toggleDialog}
aria-controls="dialog"
aria-haspopup="dialog"
aria-expanded={isDialogOpen}
aria-label="Open calendar to choose booking date"
>
📆
</button>
<p aria-live="assertive" aria-atomic="true">
{selectedDate !== undefined
? selectedDate.toDateString()
: "Please type or pick a date"}
</p>
<dialog
role="dialog"
ref={dialogRef}
id={dialogId}
aria-modal
aria-labelledby={headerId}
onClose={() => setIsDialogOpen(false)}
>
<DayPicker
month={month}
onMonthChange={setMonth}
initialFocus
mode="single"
selected={selectedDate}
onSelect={handleDayPickerSelect}
footer={
<p aria-live="assertive" aria-atomic="true">
{selectedDate !== undefined && (
<>Selected: {selectedDate.toDateString()}</>
)}
</p>
}
/>
</dialog>
</div>
);
}