DatePicker
A date picker combines a DateField and a Calendar popover to allow users to enter or select a date and time value.
import { CalendarDate } from "@internationalized/date"import { DatePicker } from "@opengovsg/oui"export const Example = () => { return ( <DatePicker label="Birth date" placeholderValue={new CalendarDate(2019, 7, 27)} /> )}Usage
Use DatePicker for a date input with a calendar popover. Use DateField for keyboard-only entry (no popover). Use DateRangePicker for a date range with a calendar. Use Calendar for inline (non-popover) date selection.
import { DatePicker } from "@opengovsg/oui"<DatePicker label="Appointment date" />Alternatively, install the component as local source via the shadcn CLI:
npx shadcn@latest add https://oui.open.gov.sg/r/date-picker.jsonpnpm dlx shadcn@latest add https://oui.open.gov.sg/r/date-picker.jsonnpx shadcn@latest add https://oui.open.gov.sg/r/date-picker.jsonbunx --bun shadcn@latest add https://oui.open.gov.sg/r/date-picker.jsonDatePicker combines a DateField keyboard input with a Calendar in a popover, built on React Aria's DatePicker. Date values use objects from the @internationalized/date package.
If no visible label is provided, an aria-label or aria-labelledby prop must be passed to identify the field to assistive technology.
Examples
Disabled
import { CalendarDate } from "@internationalized/date"import { DatePicker } from "@opengovsg/oui"export const Example = () => { return ( <DatePicker isDisabled label="Birth date" placeholderValue={new CalendarDate(2019, 7, 27)} /> )}Read Only
import { CalendarDate } from "@internationalized/date"import { DatePicker } from "@opengovsg/oui"export const Example = () => { return ( <DatePicker isReadOnly label="Birth date" defaultValue={new CalendarDate(2019, 7, 27)} /> )}Required
import { CalendarDate } from "@internationalized/date"import { DatePicker } from "@opengovsg/oui"export const Example = () => { return ( <DatePicker isRequired label="Birth date" placeholderValue={new CalendarDate(2019, 7, 27)} /> )}With Description
You can add a description to the DatePicker by passing the description property.
import { CalendarDate } from "@internationalized/date"import { DatePicker } from "@opengovsg/oui"export const Example = () => { return ( <DatePicker label="Birth date" placeholderValue={new CalendarDate(2019, 7, 27)} description="Please enter your birth date." /> )}With Error Message
You can combine the isInvalid and errorMessage properties to show an invalid input.
import { DatePicker } from "@opengovsg/oui"export const Example = () => { return ( <DatePicker label="Birth date" isInvalid errorMessage="Please enter a valid date" /> )}With Time Fields
If the value (or defaultValue) given to DatePicker includes time information, time fields will be shown in the DateField.
import { CalendarDateTime } from "@internationalized/date"import { DatePicker } from "@opengovsg/oui"export const Example = () => { return ( <DatePicker label="Event date" placeholderValue={new CalendarDateTime(2019, 7, 27, 12, 30)} /> )}Selector Icon
You can use the selectorIcon prop to change the icon used for the calendar button.
import { CalendarDate } from "@internationalized/date"import { CalendarDays } from "lucide-react"import { DatePicker } from "@opengovsg/oui"export const Example = () => { return ( <DatePicker label="Birth date" placeholderValue={new CalendarDate(2019, 7, 27)} selectorIcon={<CalendarDays />} /> )}Controlled
You can use the value and onChange properties to control the input value.
import type { CalendarDate } from "@internationalized/date"import { useState } from "react"import { getLocalTimeZone, parseDate } from "@internationalized/date"import { useDateFormatter } from "@react-aria/i18n"import { DatePicker } from "@opengovsg/oui"export const Example = () => { const [value, setValue] = useState<CalendarDate | null>( parseDate("2024-04-04"), ) const formatter = useDateFormatter({ dateStyle: "full" }) return ( <div className="flex w-full flex-row gap-2"> <div className="flex w-full flex-col gap-y-2"> <DatePicker label="Date (controlled)" value={value} onChange={setValue} /> <p className="text-default-500 text-sm"> Selected date:{" "} {value ? formatter.format(value.toDate(getLocalTimeZone())) : "--"} </p> </div> </div> )}Time Zones
DatePicker is timezone aware when a ZonedDateTime object is provided as the value. In this case, the time zone abbreviation is displayed,
and time zone concerns such as daylight saving time are taken into account when the value is manipulated.
@internationalized/date includes functions for parsing strings in multiple formats into ZonedDateTime objects.
npm install @internationalized/dateimport { parseZonedDateTime } from "@internationalized/date"import { parseAbsoluteToLocal, parseZonedDateTime,} from "@internationalized/date"import { DatePicker } from "@opengovsg/oui"export const Example = () => { return ( <div className="flex w-full flex-col gap-4"> <DatePicker defaultValue={parseZonedDateTime( "2025-11-04T03:45[America/Los_Angeles]", )} label="Event date" /> <DatePicker defaultValue={parseAbsoluteToLocal("2025-11-04T03:45Z")} label="Event date" /> </div> )}Granularity
The granularity prop allows you to control the smallest unit that is displayed by DatePicker By default, the value is displayed with "day" granularity (year, month, and day),
and CalendarDateTime and ZonedDateTime values are displayed with "minute" granularity.
@internationalized/date includes functions for parsing strings in multiple formats into ZonedDateTime objects.
npm install @internationalized/dateimport { DateValue, now, parseAbsoluteToLocal } from "@internationalized/date"
import { useDateFormatter } from "@react-aria/i18n"import { useState } from "react"import { parseAbsoluteToLocal } from "@internationalized/date"import { useDateFormatter } from "@react-aria/i18n"import { DatePicker } from "@opengovsg/oui"export const Example = () => { const [date, setDate] = useState(parseAbsoluteToLocal("2025-11-04T03:45:00Z")) const formatter = useDateFormatter({ dateStyle: "short", timeStyle: "long", }) return ( <div className="flex w-full flex-col gap-4"> <DatePicker granularity="second" label="Date and time" value={date} onChange={(value) => value && setDate(value)} /> <p className="text-default-500 text-sm"> Selected date and time: {date ? formatter.format(date.toDate()) : "--"} </p> </div> )}Min Date And Max Date
The minValue and maxValue props can also be used to ensure the value is within a specific range.
You may also want to use Calendar's isDateUnavailable (via calendarProps) prop to visually indicate which dates are unavailable for selection in the calendar.
@internationalized/date includes functions for parsing strings in multiple formats into ZonedDateTime objects.
npm install @internationalized/dateimport { getLocalTimeZone, parseDate, today } from "@internationalized/date"import { getLocalTimeZone, today } from "@internationalized/date"import { Form } from "react-aria-components"import { Button, DatePicker } from "@opengovsg/oui"export const Example = () => { return ( <Form className="flex w-full flex-col gap-4"> <DatePicker defaultValue={today(getLocalTimeZone()).subtract({ days: 1 })} minValue={today(getLocalTimeZone())} calendarProps={{ isDateUnavailable: (date) => date < today(getLocalTimeZone()), }} label="Min date" /> <DatePicker defaultValue={today(getLocalTimeZone()).add({ days: 1 })} label="Max date" maxValue={today(getLocalTimeZone())} /> <Button className="w-fit" type="submit"> Submit </Button> </Form> )}International Calendar
DatePicker supports selecting dates in many calendar systems used around the world,
including Gregorian, Hebrew, Indian, Islamic, Buddhist, and more.
Dates are automatically displayed in the appropriate calendar system for the user's locale.
The calendar system can be overridden using the https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/calendar#adding_a_calendar_in_the_locale_string,
passed to the I18nProvider component.
@internationalized/date includes functions for parsing strings in multiple formats into ZonedDateTime objects.
npm install @internationalized/dateimport { DateValue, now, parseAbsoluteToLocal } from "@internationalized/date"
import { I18nProvider } from "@react-aria/i18n"import type { DateValue } from "@internationalized/date"import { useState } from "react"import { now } from "@internationalized/date"import { I18nProvider } from "@react-aria/i18n"import { DatePicker } from "@opengovsg/oui"export const Example = () => { const [date, setDate] = useState<DateValue>(now("America/New_York")) return ( <div className="flex w-full flex-col gap-4"> <I18nProvider locale="hi-IN-u-ca-indian"> <DatePicker label="Date" value={date} onChange={(value) => value && setDate(value)} /> </I18nProvider> </div> )}Unavailable Dates
DatePicker supports marking certain dates as unavailable. These dates cannot be selected by the user and are displayed with a crossed out appearance in the calendar. In the date field, an invalid state is displayed if a user enters an unavailable date.
@internationalized/date includes functions for parsing strings in multiple formats into ZonedDateTime objects.
npm install @internationalized/dateimport { getLocalTimeZone, isWeekend, today } from "@internationalized/date"
import { useLocale } from "@react-aria/i18n"import { getLocalTimeZone, isWeekend, today } from "@internationalized/date"import { useLocale } from "@react-aria/i18n"import { DatePicker } from "@opengovsg/oui"export const Example = () => { const { locale } = useLocale() return ( <DatePicker label="Appointment date" minValue={today(getLocalTimeZone())} isDateUnavailable={(date) => isWeekend(date, locale)} /> )}Visible Months
By default, the calendar popover displays a single month.
You can use the calendarProps#visibleDuration to pass a custom duration to display multiple months at once.
import { CalendarDate } from "@internationalized/date"import { DatePicker } from "@opengovsg/oui"export const Example = () => { return ( <DatePicker label="Event date" placeholderValue={new CalendarDate(2019, 7, 27)} calendarProps={{ visibleDuration: { months: 2 }, }} /> )}Custom first day of the week
By default, the first day of the week is determined by the user's locale. You can override this using the firstDayOfWeek prop.
The available values are 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', and 'sat'.
import { CalendarDate } from "@internationalized/date"import { DatePicker } from "@opengovsg/oui"export const Example = () => { return ( <DatePicker label="Event date" placeholderValue={new CalendarDate(2019, 7, 27)} firstDayOfWeek="mon" /> )}Page Behavior
By default, when pressing the next or previous buttons, pagination will advance by a single month.
This behavior can be changed to page by the visible months instead, by setting pageBehavior to "visible".
import { CalendarDate } from "@internationalized/date"import { DatePicker } from "@opengovsg/oui"export const Example = () => { return ( <DatePicker label="Event date" placeholderValue={new CalendarDate(2019, 7, 27)} pageBehavior="visible" calendarProps={{ visibleDuration: { months: 2 }, }} /> )}Validation
See the Forms guide for end-to-end patterns including React Hook Form + Zod.
isRequired— Marks the field as required; validation fires on form submit (native) or immediately (aria).validate— A custom function(value: DateValue | null) => ValidationError | true | null | undefined. Return a string to show as the error.isInvalid+errorMessage— Use together for realtime validation feedback.errorMessageaccepts a string or a function(validation: ValidationResult) => string.minValue/maxValue— Constrain the accepted date range.validationBehavior—"native"(default) blocks form submission when invalid;"aria"marks the field via ARIA attributes only (needed for React Hook Form).
Events
onChange— Called when the date value changes, receiving the newDateValue(ornull).onOpenChange— Called when the calendar popover opens or closes, receiving a boolean.onFocus/onBlur— Called when the field receives or loses focus.onFocusChange— Called when the focus state changes, receiving a boolean.
Accessibility
- The date field segments use
role="spinbutton"with appropriate ARIA labels for each part (year, month, day). - The calendar button opens the calendar popover; the popover is labelled via
aria-labelautomatically. - Keyboard interaction: Tab to move between segments and the calendar button; type digits to update segments; Arrow Up/Down to increment/decrement; Enter on the calendar button opens the popover.
- The label, description, and error message are linked via
aria-labelledbyandaria-describedby. - If no visible label is provided, pass
aria-labeloraria-labelledby.
Slots
Slots are named regions of the component you can target with custom Tailwind classes via the classNames prop. Each slot below corresponds to a key on the classNames object.
- base: Input wrapper, it handles alignment, placement, and general appearance.
- group: Wrapper for the input and calendar button.
- calendarButton: The calendar button element.
- selectorIcon: Selector icon element.
- dialog: The calendar dialog container inside the popover element.
- input: The date input component element.
You can also override styles of the various components used within DatePicker:
Calendar slots
Slots can be overridden via the classNames.calendar prop or calendarProps#classNames prop.
Popover slots
Slots can be overridden via the classNames.popover prop or popoverProps#classNames prop.
FieldError slots
Slots can be overridden via the classNames.error prop.
Props
DatePicker
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | - | The visible label for the field |
description | string | - | Helper text shown below the field |
errorMessage | string | ((validation: ValidationResult) => string) | - | Error message displayed when the field is invalid |
value | DateValue | null | - | The current date value (controlled) |
defaultValue | DateValue | null | - | The default date value (uncontrolled) |
onChange | (value: DateValue | null) => void | - | Called when the date value changes |
isDisabled | boolean | false | Whether the field is disabled |
isReadOnly | boolean | false | Whether the field is read-only |
isRequired | boolean | false | Whether the field is required |
isInvalid | boolean | false | Whether the field should display as invalid |
validate | (value: DateValue | null) => ValidationError | true | null | undefined | - | Custom validation function |
validationBehavior | "native" | "aria" | "native" | Whether to use native HTML form validation or ARIA-only |
selectorIcon | ReactNode | - | Custom icon for the calendar trigger button |
calendarProps | CalendarProps<T> | - | Props forwarded to the internal Calendar component |
calendarButtonProps | ButtonProps | - | Additional props spread to the calendar trigger button (escape hatch) |
popoverProps | PopoverProps | - | Props forwarded to the internal Popover component |
classNames | SlotsToClasses<DatePickerSlots | "description"> & { calendar?: ..., error?: ..., popover?: ... } | - | Custom Tailwind classes for each slot, including nested Calendar, FieldError, and Popover slots |
Inherits all remaining props from React Aria's DatePicker. Notable ones: autoFocus, granularity, hideTimeZone, hourCycle, pageBehavior, shouldCloseOnSelect.
DateField
A date field allows users to enter and edit date and time values using a keyboard. Each part of a date value is displayed in an individually editable segment.
DateRangePicker
A date range picker combines two DateFields and a RangeCalendar popover to allow users to enter or select a date and time range.