A React date picker component gives users a calendar to choose a date from instead of a free-text field. You have two realistic options: the browser’s own <input type="date">, which costs nothing and looks different in every browser, or the react-datepicker library, which is the one most projects mean when they say “React date picker”. This guide builds both, with real screenshots of each running, and covers formats, minimum and maximum dates, filtering out weekends, date ranges, time selection and a custom input.
Every example was built and run with React 19.3.0, react-datepicker 9.1.0 and Vite 8.3.0, and each screenshot is the actual component in a browser, not a mock-up. Reference: the react-datepicker documentation.
Install react-datepicker
The library ships its own stylesheet. Install it, then import both the component and the CSS once, usually in the component that uses it:
npm install react-datepicker
# the date helpers it uses internally
npm install date-fns
import "react-datepicker/dist/react-datepicker.css";
import DatePicker from "react-datepicker";
Without that CSS import the calendar still works, but it renders as an unstyled list and looks broken, which is the most common first problem people hit.
A basic React date picker component
The component is controlled: you keep the selected date in state and pass it as selected, and onChange hands you a Date object (or null when the field is cleared):
import { useState } from "react";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
export default function App() {
const [startDate, setStartDate] = useState(new Date("2026-03-14T00:00:00"));
return (
<div style={{ fontFamily: "system-ui", padding: 16 }}>
<label htmlFor="delivery">Delivery date</label>
<DatePicker
id="delivery"
selected={startDate}
onChange={(date) => {
setStartDate(date);
console.log("selected:", date?.toDateString());
console.log("toISOString:", date?.toISOString().slice(0, 10)); // careful: UTC
console.log("local date :", date && `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`);
}}
/>
</div>
);
}
Console output while picking a date:
selected: Fri Mar 20 2026
toISOString: 2026-03-19
local date : 2026-03-20

Look closely at that output, because it catches almost everyone. The user picked 20 March, and toISOString().slice(0, 10) reported 2026-03-19. The picker stores local midnight, toISOString() converts to UTC, and west of Greenwich that lands on the previous day. Build the string from the local parts instead, as the third line does, or use date-fns: format(date, "yyyy-MM-dd"). Sending the ISO string straight to an API is how bookings end up one day out.
Change the date format shown in the field
dateFormat controls the text in the input, using date-fns patterns: MM/dd/yyyy for US style, dd/MM/yyyy for most of the rest of the world, MMM d, yyyy for “Mar 14, 2026”. Add isClearable for the small clear button, and the dropdown props to jump years quickly:
import { useState } from "react";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
export default function App() {
const [date, setDate] = useState(new Date("2026-03-14T00:00:00"));
return (
<div style={{ fontFamily: "system-ui", padding: 16 }}>
<DatePicker
id="invoice"
selected={date}
onChange={setDate}
dateFormat="MM/dd/yyyy" // what the input shows
placeholderText="Select a date"
isClearable // adds the small x button
showMonthDropdown
showYearDropdown
dropdownMode="select"
/>
<p>Value in state: {date ? date.toString().slice(0, 15) : "none"}</p>
</div>
);
}

showMonthDropdown and showYearDropdown save a lot of clicking for birth dates.Restrict which dates can be picked
Three props cover nearly every rule: minDate and maxDate for the window, filterDate for a rule such as weekdays only, and excludeDates for specific days like public holidays. Disabled days are greyed out and cannot be clicked:
import { useState } from "react";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
const isWeekday = (date) => {
const day = date.getDay();
return day !== 0 && day !== 6; // Sunday = 0, Saturday = 6
};
export default function App() {
const [date, setDate] = useState(null);
return (
<div style={{ fontFamily: "system-ui", padding: 16 }}>
<DatePicker
id="appointment"
selected={date}
onChange={setDate}
placeholderText="Weekdays only"
minDate={new Date("2026-03-09T00:00:00")}
maxDate={new Date("2026-03-27T00:00:00")}
filterDate={isWeekday}
excludeDates={[new Date("2026-03-17T00:00:00")]}
openToDate={new Date("2026-03-14T00:00:00")}
/>
</div>
);
}

filterDate, 17 March is excluded, and everything outside the range is dimmed.filterDate runs for every visible day, so keep it cheap: a lookup in a Set rather than a search through an array.
Pick a date range
For check-in and check-out style fields, selectsRange turns one picker into a range picker. onChange then receives an array of two dates, the second being null until the user clicks the end date:
import { useState } from "react";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
export default function App() {
const [range, setRange] = useState([
new Date("2026-03-09T00:00:00"),
new Date("2026-03-13T00:00:00"),
]);
const [start, end] = range;
return (
<div style={{ fontFamily: "system-ui", padding: 16 }}>
<DatePicker
id="stay"
selectsRange
startDate={start}
endDate={end}
onChange={(dates) => {
setRange(dates);
console.log("range:", dates.map((d) => d && d.toDateString()));
}}
monthsShown={1}
dateFormat="MMM d, yyyy"
/>
<p>{start && end ? `${start.toDateString()} to ${end.toDateString()}` : "Pick two dates"}</p>
</div>
);
}

Add a time picker
showTimeSelect puts a scrollable list of times beside the calendar. timeIntervals sets the step in minutes, and the dateFormat needs a time part or the chosen time will not show in the field:
import { useState } from "react";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
export default function App() {
const [slot, setSlot] = useState(new Date("2026-03-14T09:30:00"));
return (
<div style={{ fontFamily: "system-ui", padding: 16 }}>
<DatePicker
id="slot"
selected={slot}
onChange={(date) => {
setSlot(date);
console.log("slot:", date?.toLocaleString("en-US"));
}}
showTimeSelect
timeIntervals={30}
timeCaption="Time"
dateFormat="MM/dd/yyyy h:mm aa"
/>
</div>
);
}

h:mm aa giving the 9:30 AM style.Custom input: a button instead of a text field
customInput replaces the field with your own element, which is how you match a design system. The library passes it value, onClick and a ref. In React 19 ref is an ordinary prop, so this no longer needs forwardRef; on React 18 and earlier, wrap the component in forwardRef((props, ref) => ...) or the calendar will not position itself correctly:
import { useState } from "react";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
// React 19: ref is a normal prop, so forwardRef is no longer needed here.
function DateButton({ value, onClick, ref }) {
return (
<button id="picker-button" onClick={onClick} ref={ref}
style={{ fontFamily: "system-ui", fontSize: 15, padding: "8px 14px",
border: "1px solid #0b6bcb", borderRadius: 6, background: "#eef5ff", cursor: "pointer" }}>
{value || "Choose a date"}
</button>
);
}
export default function App() {
const [date, setDate] = useState(new Date("2026-03-14T00:00:00"));
return (
<div style={{ fontFamily: "system-ui", padding: 16 }}>
<DatePicker
selected={date}
onChange={setDate}
customInput={<DateButton />}
dateFormat="EEEE, MMMM d, yyyy"
/>
</div>
);
}

The no-dependency option: input type=”date”
If you do not need a custom look, the browser already has a date picker. It is one line, it is accessible, and it needs no library at all. The catch is that the calendar is drawn by the browser, so it looks different in Chrome, Safari and Firefox, and the value is a "YYYY-MM-DD" string rather than a Date:
import { useState } from "react";
export default function App() {
const [date, setDate] = useState("2026-03-14"); // always a "YYYY-MM-DD" string
return (
<div style={{ fontFamily: "system-ui", padding: 16 }}>
<label htmlFor="native">Delivery date</label>{" "}
<input
id="native"
type="date"
value={date}
min="2026-03-01"
max="2026-03-31"
onChange={(event) => {
setDate(event.target.value);
console.log("input value:", event.target.value);
console.log("as a Date:", new Date(`${event.target.value}T00:00:00`).toDateString());
}}
style={{ fontSize: 15, padding: 6 }}
/>
<p>Selected: {date || "nothing yet"}</p>
</div>
);
}

Watch the conversion: new Date("2026-03-20") is parsed as UTC and can come out a day earlier in a negative timezone, which is why the example appends T00:00:00 to force local time.
<input type="date"> | react-datepicker | |
|---|---|---|
| Bundle cost | None |