react-datepicker
react-datepicker is a controlled React date and time picker built around native JavaScript Date objects and date-fns. It supplies the text input, floating calendar, keyboard navigation, date ranges, multiple selection, month and year pickers, time lists, localization, portals, inline calendars, blocked or highlighted dates, and custom headers or inputs. Version 9 supports React 16.9 through 19 and includes TypeScript declarations and CSS. It is a styled component rather than a headless calendar primitive, and form serialization remains your responsibility.
A capable default when a React application wants a styled, controlled Date-based picker with many modes and can accept date-fns plus supplied CSS. Avoid it when date-only serialization, headless styling, or an existing UI-system integration matters more than its long feature list.
Use it if
- You want a mature drop-in React calendar with single-date, range, multiple-date, and date-time modes
- Your application already represents values as JavaScript Date objects and uses date-fns conventions
- You need keyboard navigation, locale registration, constrained dates, custom inputs, portals, or inline calendars from one component
- You prefer adapting supplied CSS and props over assembling a calendar from headless primitives
- Your domain stores plain calendar dates such as birthdays as `YYYY-MM-DD` strings: the component returns Date objects at local time, and its timezone guide documents the familiar one-day shift when callers use `toISOString()` without a date-only conversion
- You want a headless primitive with complete markup and styling ownership: react-datepicker ships its own DOM structure and stylesheet, while react-day-picker is a better base for a design system
- You need one component library with date fields matching an existing Material UI theme: MUI X Date Pickers integrates with that ecosystem instead of requiring a separate CSS skin
- You cannot take date-fns into the dependency graph: version 9.1.0 depends on date-fns 4.1 and declares date-fns-tz 3 as a peer for the `timeZone` feature
- You need flawless legacy or exotic browser coverage: the README promises current Chrome and Firefox, still mentions IE10-era support, and says noncurrent browsers may break as development continues; test the actual browsers and assistive technology in your support matrix
Setup reality
Install with `npm install react-datepicker`, make sure compatible `react` and `react-dom` peers are already present, and import `react-datepicker/dist/react-datepicker.css` once in the client bundle unless you are replacing every style yourself. Version 9.1.0 supports React 16.9 through 19, depends on date-fns 4.1, declares date-fns-tz 3 as a peer, and ships CommonJS, ESM, and TypeScript declarations. The component is controlled: keep a `Date | null` in state, pass it as `selected`, and update it from `onChange`. Range and multiple selection deliberately change the callback type, so TypeScript code must model `[Date | null, Date | null]` or `Date[] | null` rather than reuse a single-date handler. The biggest first-run mistake is forgetting the CSS, which leaves a functional but effectively unstyled calendar. The second is forgetting that a JavaScript Date is a timestamp interpreted in a timezone, not a date-only value. A birthday selected as local midnight can become the previous UTC date after `toISOString()`. Format date-only values from local year, month, and day, or use date-fns `format(date, 'yyyy-MM-dd')`; reserve ISO timestamps for actual moments. Version 9 adds a `timeZone` prop for IANA zones, but the repository guide says it needs date-fns-tz and falls back to local-time behavior with a development warning when that package is absent. Keep `minDate`, `maxDate`, excluded dates, and selected values consistent with the same timezone model. Non-English display is not automatic: import a locale object from date-fns, call `registerLocale`, then pass its registered name or set a global default. Popovers can be clipped by overflow containers; `withPortal`, `portalId`, and `portalHost` exist, but moving DOM also affects stacking, focus, and test selectors. Custom inputs must forward the ref and received input props or opening and keyboard focus break. Time selection defaults to 30-minute intervals. Date disabling functions run across rendered days, so expensive business rules should be memoized or converted into interval and date props. The README documents arrow, Page Up, Home, End, Enter, Escape, and Tab behavior, but built-in keyboard support does not replace application-level accessibility testing, clear labels, validation messages, and touch testing.
Patterns
Build a controlled single-date pickerselect-single-date
import { useState } from 'react';
import DatePicker from 'react-datepicker';
import 'react-datepicker/dist/react-datepicker.css';
export function DueDate() {
const [date, setDate] = useState<Date | null>(null);
return <DatePicker selected={date} onChange={setDate} placeholderText="Choose a date" />;
}Import the stylesheet once in the application and keep null in the state type so clearing is valid.
Select a start and end dateselect-date-range
const [range, setRange] = useState<[Date | null, Date | null]>([null, null]);
const [startDate, endDate] = range;
<DatePicker
selectsRange
startDate={startDate}
endDate={endDate}
onChange={(next) => setRange(next)}
isClearable
/>With `selectsRange`, onChange receives a tuple rather than the single Date used by the default mode.
Select several independent datesselect-multiple-dates
const [dates, setDates] = useState<Date[] | null>([]);
<DatePicker
selectsMultiple
selectedDates={dates ?? []}
onChange={(next) => setDates(next)}
/>Multiple selection has its own Date-array callback type; do not combine it with `selectsRange`.
Select a date and timeadd-time-picker
<DatePicker
selected={appointment}
onChange={setAppointment}
showTimeSelect
timeIntervals={15}
dateFormat="Pp"
/>The default time interval is 30 minutes. The resulting Date represents a moment, so ISO serialization is appropriate only after confirming timezone intent.
Register and use a date-fns localeregister-locale
import DatePicker, { registerLocale } from 'react-datepicker';
import { es } from 'date-fns/locale/es';
registerLocale('es', es);
<DatePicker selected={date} onChange={setDate} locale="es" />Registration is explicit; passing an unregistered name does not load locale data automatically.
Display and select in an IANA timezoneset-timezone
<DatePicker
selected={instant}
onChange={setInstant}
showTimeSelect
timeZone="America/New_York"
dateFormat="MMMM d, yyyy h:mm aa"
/>Install a compatible date-fns-tz 3.x peer. Without it, development logs a warning and the picker falls back to local timezone behavior.
Serialize a date without a timezone shiftserialize-date-only
import { format } from 'date-fns';
function handleBirthday(date: Date | null) {
const value = date ? format(date, 'yyyy-MM-dd') : null;
saveBirthday(value);
}
<DatePicker selected={birthday} onChange={handleBirthday} />Do not use `toISOString().slice(0, 10)` for a local calendar date; UTC conversion can move it to the previous or next day.
Restrict selectable dateslimit-date-range
import { addMonths, startOfToday } from 'date-fns';
const today = startOfToday();
<DatePicker
selected={date}
onChange={setDate}
minDate={today}
maxDate={addMonths(today, 6)}
/>Provide selected, minimum, and maximum values under the same timezone assumptions, particularly when also using `timeZone`.
Disable weekends and known blackout datesdisable-business-days
import { isWeekend, isSameDay } from 'date-fns';
const blackout = [new Date(2026, 11, 25)];
<DatePicker
selected={date}
onChange={setDate}
filterDate={(day) => !isWeekend(day) && !blackout.some((x) => isSameDay(x, day))}
/>filterDate runs for rendered calendar days; memoize large rule sets or use excludeDates and interval props to avoid repeated expensive work.
Render the calendar without an input popoverrender-inline-calendar
<DatePicker
inline
selected={date}
onChange={setDate}
onClickOutside={() => setPanelOpen(false)}
/>The README specifically points to `onClickOutside` for closing an inline picker because there is no input popover to manage it.
Supply a custom input that forwards its refuse-custom-input
const DateButton = forwardRef<HTMLButtonElement, React.ComponentProps<'button'>>(
({ value, onClick, ...props }: any, ref) => (
<button type="button" ref={ref} onClick={onClick} {...props}>{value || 'Choose date'}</button>
)
);
<DatePicker selected={date} onChange={setDate} customInput={<DateButton />} />Forward the ref, value, click handler, and accessibility props injected by DatePicker or focus and keyboard behavior can fail.
Move the calendar out of a clipped containerrender-in-portal
<DatePicker
selected={date}
onChange={setDate}
withPortal
portalId="date-picker-portal"
/>A portal helps with overflow clipping, but verify focus order, stacking context, modal interaction, and test selectors after moving the DOM.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| react-day-picker | npm | Choose it when a design system needs a more headless calendar surface and can supply its own input and popover behavior |
| @mui/x-date-pickers | npm | Choose it when the application already uses Material UI and date fields must share its theme, adapters, and form conventions |
| flatpickr | npm | Choose it for framework-neutral pages or mixed stacks where a React-specific controlled component is unnecessary |