build69
build69  /  Components  /  Date & Time  /  DatePicker

DatePicker

v1.0.18Updated 19 Oct 2026

Date, date-range, and date-time pickers built on Calendar + Popover. Supports from/to constraints, a presets footer slot, and the Thai Buddhist Era calendar (พ.ศ.) with full Thai locale labels.

Preview

Live · 14 stories
Single DateOpen ↗
▸Why this is recommendeddatepickerdatepickercalendar+5

DatePicker — single date.

Recommended for+14
  • ✓Picking a single date in a form
  • ✓Birth date or invoice date entry
  • ✓Thai invoice date entry in Buddhist Era (พ.ศ.)
  • ✓Filtering a table by one date
  • ✓Booking a single-day appointment
  • ✓Choosing a deadline or due date
Tags+6
datepickerdatepickercalendarsinglebuddhistthailocalepopover
Features+4
  • Single date selection in a Popover
  • fromDate / toDate constraints (day-granularity, time-of-day safe)
  • Controlled (date) or uncontrolled (defaultDate)
  • Composable footer slot via children
  • Thai Buddhist Era calendar (calendar="buddhist") rendering พ.ศ. years
  • BE/CE era toggle chip — the choice STICKS across reopens, and is readable/settable via calendarMode (fb_20a80f6c)
  • Thai locale (locale="th") for month / weekday labels
  • Does NOT pull in Select — lighter than DateTimePicker
  • i18n: calendar month/weekday names and year digits follow the app LocaleProvider (useB69Locale) for non-Thai locales
  • Labelled by FormField: the trigger takes the id (its label names it), FormField aria-invalid now reaches it, and it fills the field column (standalone w-56)
Not recommended for−2
  • ✕Selecting a start and end date (use DateRangePicker)
  • ✕Selecting a date together with a time (use DateTimePicker)
  • ✕Always-visible inline month view (use Calendar directly)
  • ✕Scheduling grid (use BuildCalendar)

Install

Ask your agent, or call the MCP tool directly. The resolved bundle is written into your project.

get_registry_item({ name: "DatePicker" })

Usage

import { DatePicker } from '@/components/inputs/date-picker';

<DatePicker date={date} onDateChange={setDate} fromDate={new Date()} />

Tick variants in the left menu — each adds a tab with that variant’s real story source, the same code the preview above renders.

Props

PropTypeRequired
date

Selected date (controlled)

Date—
onDateChange

Selection callback

(date: Date | undefined) => void—
value

Alias of `date`, matching the value/onValueChange convention of the other inputs; `date` wins when both are set

Date—
onValueChange

Alias of `onDateChange`; fired alongside it

(date: Date | undefined) => void—
defaultDate

Initial value for uncontrolled usage (ignored when `date`/`value` is provided)

Date—
placeholder

Trigger text when no date is selected (default "Pick a date")

string—
fromDate

Min selectable date

Date—
toDate

Max selectable date

Date—
formatStr

date-fns format string (default "MMM d, yyyy")

string—
align

Popover alignment

'start' | 'center' | 'end'—
children

Footer content rendered under the calendar

React.ReactNode—
calendar

The era the field shows by DEFAULT (default "gregory"); "buddhist" renders พ.ศ. years and puts the era chip in the footer. "gregorian" is the deprecated spelling — still accepted and normalised on arrival, so a host passing its own stored Intl calendar value through no longer gets TS2322 and has to translate the word by hand (fb_e8de80fb). The `data-calendar` attribute still writes "gregorian", because consumers already style and test against it

'gregory' | 'buddhist'—
calendarMode

Which era the picker is SHOWING, as a controlled value. Pair with `onCalendarModeChange` to remember the user's choice across opens or sessions. Left alone the picker keeps its own, seeded from `calendar` — and no longer resets it when the popup reopens, which is what stopped the era chip from sticking (fb_20a80f6c). Presentation only: the Date handed back is the same Date in either era. The deprecated "gregorian" spelling is accepted here too and normalised on arrival, so an era a host stored under the old word can be fed straight back in without a type error (fb_e8de80fb); `onCalendarModeChange` always reports the house spelling

'gregory' | 'buddhist'—
onCalendarModeChange

Fires with the era the user switched the picker to

(mode: 'gregory' | 'buddhist') => void—
showEraChip

Show the era chip even when `calendar` is gregorian, so a ค.ศ. field can be switched to พ.ศ. (default false — a chip appearing in footers that never had one is a change other consumers did not ask for)

boolean—
locale

Locale for labels and formatting. `locale="th"` now also supplies the Thai BUILT-IN strings (placeholder, era chip พ.ศ./ค.ศ., and for DateRangePicker the whole quick-range rail) — previously the grid localised but the trigger stayed English, which only showed on an EMPTY field. An explicit `labels` (or `placeholder`) still wins, and locale="en" is unchanged. MCP feedback fb_3adc3df4.

'en' | 'th'—
labels

Override the built-in UI strings (i18n): placeholder, be, ce, toggleCalendarEra. The three era keys are forwarded to the era chip, so a host that translates nothing else can still rename the พ.ศ./ค.ศ. text and the chip aria-label. Merged over the locale defaults (DEFAULT_DATE_PICKER_LABELS, or TH_DATE_PICKER_LABELS under locale="th"), so a partial object only replaces the keys it names; the explicit `placeholder` prop still wins over labels.placeholder

Partial<DatePickerLabels>—
disabled

Disables the trigger

boolean—
invalid

Mark the field invalid (destructive ring); aria-invalid wins when both are set

boolean—
id

DOM id set on the trigger button; FormField clones its field id in, and its label then names the control

string—
aria-label

Accessible name of the trigger when there is no visible label

string—
aria-labelledby

Id(s) of the element(s) that label the trigger; wins over a FormField label

string—
aria-describedby

Id(s) of hint or error text describing the trigger (FormField clones this in)

string—
aria-invalid

Mark the trigger invalid; FormField clones this in from its error prop

boolean—
aria-required

Accepted (FormField clones it in) but not rendered: ARIA does not allow it on a button. The label text "required" is part of the name instead

boolean—

Related