Date Picker React component for Tailwind CSS v4
v1.0.0Added in 1.0.0
A React date picker: type a date in your locale or pick it from a calendar, with presets, limits, optional time and a phone sheet.
Type a date the way your locale writes it: "09232026" becomes "09/23/2026" as you type (separators appear after each group), "9/23" means this year, a 2 digit year picks the nearest century, ISO "2026-09-23" always works, "+3" is three days from today, and keywords like "today", "tomorrow" or "yesterday" work (other languages through parseKeywords). Clicking the field opens the calendar under it without taking focus from the text; while you type, the calendar follows and highlights the typed day, and a complete, allowed date is committed at once. On blur the text is re-written in the display format; text that does not parse shows invalidDateText, and a day outside min / max or a disabled day shows outOfRangeText. Escape in the field brings back the last good date. Clicking a day (or a preset chip: Today, Tomorrow, Next week or your own) sets it and closes the panel; with withTime a time field sits under the calendar and Done closes. The calendar button toggles the panel; the clear button empties the field. Keyboard: ArrowDown (or Alt+ArrowDown) opens the panel and moves focus to the selected day (else today); arrows move a day or a week, Home / End the week's ends, PageUp / PageDown a month (Shift: a year), Enter or Space picks and returns focus to the field, Escape closes without changing anything. With editable off the field is a button: a click, Enter, Space or ArrowDown open the calendar and a second click closes it. The calendar button opened from the keyboard moves focus to the selected day too. On a phone the field never raises the keyboard: a tap opens a bottom sheet with the title, presets as a row of chips, the calendar with 44px days you can swipe between months, and the time field when withTime (it opens its own wheel sheet); a tap on a day commits and closes, and with withTime or closeOnSelect off the sheet has Done and Clear buttons and dragging it away keeps the old date. name submits the ISO date through a hidden input.
Date Picker is a Pro React component in the React input components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add date-picker, together with Button, Calendar, Drawer, Field, Time Picker and 7 more and the npm packages @radix-ui/react-popover, date-fns, motion and lucide-react.
Add to a project
Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup
npx wingo-ui@latest add date-pickerDate Picker is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans
Usage
Import DatePicker from @/components/ui/date-picker.
import { useState } from "react"
import { addDays } from "date-fns"
import { DatePicker } from "@/components/ui/date-picker"
export function DeliveryDate() {
const [date, setDate] = useState<Date | null>(null)
return (
<DatePicker
label="Delivery date"
description="We deliver Monday to Friday."
value={date}
onValueChange={setDate}
locale="en-US"
min={addDays(new Date(), 1)}
disabled={{ dayOfWeek: [0, 6] }}
presets
presetLabels={{ nextWeek: "In a week" }}
invalidDateText="That date does not exist"
outOfRangeText="We do not deliver on that day"
sheetTitle="Delivery date"
name="delivery_date"
/>
)
}Props
labeldefault""ReactNodeThe field label, linked to the input. Plain text also titles the phone sheet.
descriptiondefault""ReactNodeHelper text under the field, linked with aria-describedby.
errordefault""ReactNodeReplaces the description with an animated error and marks the field invalid. It wins over invalidDateText and outOfRangeText.
requireddefaultfalsebooleanThe red mark after the label and required on the input.
invaliddefaultfalsebooleanThe invalid look and aria-invalid without a message. A controlled value outside min / max is flagged the same way.
disableddefaultfalseboolean or DateMatcher or DateMatcher[] (DateMatcher: Date, Date[], { from, to }, { before }, { after }, { dayOfWeek } or (date) => boolean)true dims the field, blocks typing and never opens the calendar (the hidden input is not submitted). A DateMatcher instead disables those days: a date, a list, { from, to }, { before }, { after }, { dayOfWeek: [0, 6] } for weekends, or a test function.
readOnlydefaultfalsebooleanThe date stays visible and selectable but can not change: no typing, no calendar, no clear button.
valueDate | nullThe date, controlled (null is empty). With withTime the Date carries the time. A value outside min / max shows as given and is flagged invalid.
defaultValuedefault"null"Date | nullThe starting date while uncontrolled. A form reset returns to it.
onValueChange(date: Date | null) => voidCalled with the new local Date as soon as a complete typed date is allowed, and after a day, a preset, a time, Clear or the sheet's Done. null when cleared.
placeholderdefault""stringEmpty uses the locale's pattern in lowercase ("dd.mm.yyyy", "mm/dd/yyyy"). Localized forms pass their own, like "zz.ll.aaaa" in Romanian. It also becomes the screen reader format hint.
localedefault""stringHow dates are read and written, the month and weekday names and the first day of the week: ro-RO 23.09.2026 from Monday, en-US 09/23/2026 from Sunday. Empty uses the nearest LocaleProvider, else the page's language.
displayFormatIntl.DateTimeFormatOptionsHow the value shows while the field is not focused ({ dateStyle: "long" } gives "March 5, 1990"). Typing is always numeric. Default: 2 digit day and month, numeric year.
editabledefaulttruebooleanTyping on desktop. Off, the field is a button that only opens the calendar (phones always work that way).
autoFormatdefaulttruebooleanSeparators appear as digits are typed: "2309" becomes "23.09.".
parseKeywordsPartial<Record<"today" | "tomorrow" | "yesterday", string[]>>Words that mean a day, matched without case or diacritics. English by default; another language passes its own words, e.g. { today: ["hoy"], tomorrow: ["mañana"], yesterday: ["ayer"] }.
minDateThe first day that can be picked. Earlier days are disabled, navigation stops at its month and a typed earlier date shows outOfRangeText.
maxDateThe last day that can be picked (18 years ago for a birthday).
presetsdefaultfalseboolean | { label: ReactNode; value: Date | (() => Date); key?: string }[]Chips above the calendar. true gives Today, Tomorrow and Next week; or pass your own. A preset outside the limits is disabled.
presetLabelsPartial<{ today: string; tomorrow: string; nextWeek: string }>Names of the built-in presets ("Today", "Tomorrow", "Next week").
clearabledefaulttruebooleanA clear button in the field while it has a date (and Clear in the panel footer and the sheet).
withTimedefaultfalsebooleanA time field under the calendar; the value keeps both and the field reads "24.09.2026, 14:30". Picking a day keeps the panel open until Done. Clearing keeps the time for the next pick.
timeStepdefault"15"numberMinute step of the time field.
hourCycledefault"auto""auto" | "h12" | "h23"12 or 24 hours for the time; auto follows the locale.
timePropsPartial<TimePickerProps>Anything else for the time field: labels, min, max, disabledTimes, showNow.
closeOnSelectdefaulttruebooleanPicking a day closes the panel. Never with withTime. Off, the panel (and the sheet) get a Done button.
responsivedefaulttruebooleanBelow 768px the field opens a bottom sheet instead of the popover. Off, phones get the popover and can type.
weekStartsOndefault-10 | 1 | 2 | 3 | 4 | 5 | 6 | -1The first day of the week: 0 Sunday ... 6 Saturday; -1 follows the locale (Monday in ro-RO, Sunday in en-US).
fixedWeeksdefaulttruebooleanSix weeks in every month, so the panel never changes height while you browse.
monthYearPickerdefaulttruebooleanThe calendar caption opens a months grid, then a years grid, for fast jumps (birthdays).
numberOfMonthsdefault1numberMonths side by side in the popover (1 to 3). The phone sheet always shows one.
events{ date: Date; tone?: Tone; color?: string; label?: string }[]Dots under days (deliveries, deadlines); labels join the day's aria-label.
calendarPropsPartial<CalendarProps>Anything else for the calendar: size, radius, strikeDisabled, showWeekNumbers, todayButton, renderDay, labels.
invalidDateTextdefault"Enter a valid date"stringThe error after blur while the text does not parse ("31.02.2026").
outOfRangeTextdefault"This date is not available"stringThe error after blur for a typed day outside min / max or a disabled day.
defaultOpendefaultfalsebooleanStarts with the calendar (or the sheet) open. Pass open and onOpenChange to control it.
openbooleanWhether the calendar is open, controlled. It never opens while disabled or readOnly.
onOpenChange(open: boolean) => voidCalled when the popover or sheet opens or closes.
sizedefault"md""sm" | "md" | "lg"Field heights of 32, 40 and 48px (md is 44px on touch screens), with matching text.
variantdefault"soft""soft" | "outline" | "underline" | "ghost"The field box: a tinted fill, a border, a bottom line that grows on focus, or nothing until hover.
radiusdefault"md""none" | "sm" | "md" | "lg" | "full"Corner radius of the field, scaled per size like the button. Ignored by underline.
tonedefault"neutral""neutral" | "brand" | "success" | "warning" | "danger" | "info"The selected day, the chosen preset and the Done button.
colordefault""stringAny CSS color for the selection; replaces the tone.
iconReactNodeThe calendar button's icon.
sheetTitledefault"Select date"stringThe phone sheet's title when the label is not plain text (or missing).
labelsPartial<{ clear; open; done; dialog; time; presets; format: string }>Every other string, merged over the English defaults ("Clear", "Open calendar", "Done", "Choose date", "Time", "Quick picks", "Format: {format}"). format is the screen reader hint after the field.
namedefault""stringA hidden input with this name and the local ISO date ("2026-09-23", "2026-09-23T14:30" with withTime), never shifted to UTC.
sidedefault"bottom""top" | "right" | "bottom" | "left"Where the popover opens; it flips when there is no room.
aligndefault"start""start" | "center" | "end"How the popover lines up with the field.
containerHTMLElement | nullPortal target of the popover and the sheet; defaults to document.body.
fieldPropsPartial<FieldProps>Anything else for the Field wrapper: optional, labelAction, orientation, labelWidth, hideLabel.
idstringThe input's id; generated when left out.
classNamesPartial<Record<"field" | "label" | "description" | "error" | "counter" | "box" | "input" | "trigger" | "clear" | "content" | "presets" | "preset" | "calendar" | "time" | "footer", string>>Extra classes for the inner parts. content is the popover (or the sheet).
classNamestringExtra classes for the outermost element: the Field when there is a label or message, else the box.
refRef<HTMLInputElement> (a useRef object or an (el) => void callback)The text input. Other props (onBlur, autoFocus, aria-*, data-*) land on it too.
Parts
DatePreset
typeOne preset button: { label, value: Date | (() => Date), key? }.
Examples
Birthday
max 18 years ago, the long display format ("March 5, 1990") and the month / year grid for jumping decades.
With time
withTime adds a time field under the calendar; the field reads the date and time ("mm/dd/yyyy, 2:30 PM") and the hidden input sends the ISO date-time ("yyyy-mm-ddT14:30").
Typed formats
One value in ro-RO (23.09.2026, Monday first) and en-US (09/23/2026, Sunday first). Try 0923, 9/23, ISO, tomorrow or +3.
Errors
A date that does not exist shows invalidDateText after blur, a weekend shows outOfRangeText; the second field has a consumer error.
Holidays
Public holidays disabled and struck through with calendarProps, rounded-square days and a short weekday display.
Button only
editable={false}: a large outline field that only opens the calendar, with a custom teal selection.
Custom presets
Your own quick picks, fixed or computed when the panel opens, in the success tone.
Sizes and variants
sm, md and lg in the four field boxes.
Dependencies
Files
- components/ui/date-picker.tsx
FAQ
Is the React Date Picker component free?
The preview, props and docs are free; copying the source needs a Pro plan. See plans.
How do I install Date Picker?
Run npx wingo-ui@latest add date-picker. It also adds the Wingo UI items it builds on (Button, Calendar, Drawer, Field, Time Picker and 7 more) and the npm packages @radix-ui/react-popover, date-fns, motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/date-picker.
How does Date Picker behave on phones?
Below 768px the field opens a bottom sheet instead of the popover. Off, phones get the popover and can type. The responsive prop controls this and is on by default.
See also
Used in
More React input components
Articles using this component
- Nextjs Restaurant Template: Open Now, Menu, Book a Table
Build it: screens and apps10 min read
- Mantine vs shadcn in 2026: Which React UI Library to Pick
Comparisons and alternatives11 min read
- MUI Alternatives for React and Tailwind in 2026
Comparisons and alternatives10 min read
- React Date Range Picker With Presets and a Phone Sheet
Component guides9 min read