|Kit

DatePicker

A trigger + popover calendar on Ark UI's date-picker machine, composing Calendar's grid and TimeInput's time row — range and withTime cover all four date combinations.

DatePicker is the flagship of the dates & time family — a styled trigger that opens a popover calendar, composing Calendar's shared grid module and TimeInput's shared time row. range and withTime cover all four date combinations; DateRangePicker, DateTimePicker, and DateTimeRangePicker are thin named presets pinning those two flags with a narrowed value type.

import { DatePicker } from '@42/ui-react/date-picker';

<DatePicker label="Start date" value={date} onChange={setDate} clearable />

Playground

Component

SpinnerLoading preview

Modes

Props

Code

<Component  numOfMonths={1}  minuteStep={1}  timePlaceholder="--"/>

Single date

The default shape — a native Date | null value, exactly like Calendar's own.

import { useState } from 'react';
import { DatePicker } from '@42/ui-react/date-picker';

const [date, setDate] = useState<Date | null>(null);

<DatePicker value={date} onChange={setDate} />

Range

range selects a { start, end } pair instead of a single date — click once for the start, again for the end.

import { useState } from 'react';
import { DateRangePicker, type DateRangeValue } from '@42/ui-react/date-picker';

const [range, setRange] = useState<DateRangeValue>({ start: null, end: null });

<DateRangePicker value={range} onChange={setRange} />

With time

withTime adds an embedded HOUR/MINUTE row (TimeInput's own TimeRow) below the calendar grid; the value carries the picked hour/minute on the same Date. Combine with range for all four combinations.

<DateTimePicker value={date} onChange={setDate} />
<DateTimeRangePicker value={range} onChange={setRange} />

12h vs 24h follows the active locale by default (same rule as TimeInput) — override with format={12 | 24} or the finer hourCycle escape hatch.

// French defaults to 24h — no AM/PM segment — and formats the trigger's date/time accordingly.
<DateTimePicker locale="fr-FR" value={date} onChange={setDate} />

Presets

presets renders a sidebar of quick-pick shortcuts. presets={true} ships a mode-appropriate default set; pass an array for a fully custom list. DatePreset has three shapes: { value } (one of Ark's own range preset strings), { range } (an explicit { start, end } | null, range mode), or { date } (a single Date | null, single-date mode) — nothing stops mixing shapes for the current mode, so keep custom entries matched to range.

Range presets

Default set: Today, Yesterday, This week, Last week, This month, Last month, This year, All time.

import { DateRangePicker, type DatePreset } from '@42/ui-react/date-picker';

// Default set
<DateRangePicker presets value={range} onChange={setRange} />

// Custom — mix Ark's own string presets with explicit ranges; `range: null` clears/unbounds.
const customPresets: DatePreset[] = [
  { label: 'This week', value: 'thisWeek' },
  { label: 'This year', value: 'thisYear' },
  { label: 'All time', range: null },
];

<DateRangePicker presets={customPresets} value={range} onChange={setRange} />

Single-date presets

Default set: Today, Yesterday, Tomorrow, Next week — a different, forward-facing shape than the range set (a due date or appointment, not an analytics window), not a trimmed-down copy of it — "This week"/"This month" are inherently range-shaped and don't collapse to one date.

import { DatePicker, type DatePreset } from '@42/ui-react/date-picker';

// Default set
<DatePicker presets value={date} onChange={setDate} />

// Custom — `date: null` clears.
const customPresets: DatePreset[] = [
  { label: 'Next Friday', date: nextFriday },
  { label: 'Clear', date: null },
];

<DatePicker presets={customPresets} value={date} onChange={setDate} />

Staged commit (withConfirm)

Without withConfirm, a single date commits (and closes) on pick, and a range commits on its second click — both handled natively by Ark. With withConfirm, the popover stays open after a pick: Cancel/Apply buttons appear, and only Apply calls the outer onChange — Cancel reverts to the last committed value.

<DatePicker withConfirm value={date} onChange={setDate} />

Translations

locale drives month/day names, first-day-of-week, and the embedded time row's 12h/24h default automatically, but everything else in this popover — Ark's own accessible names, the presets={true} default labels, Cancel/Apply, and Start/End time — is English by default. translations is one prop covering all of it; it's partial, so unset keys keep their English text.

<DateRangePicker
  locale="fr-FR"
  presets
  withConfirm
  translations={{
    presets: { today: "Aujourd'hui", thisWeek: 'Cette semaine' },
    cancel: 'Annuler',
    apply: 'Valider',
    startTime: 'Début',
    endTime: 'Fin',
  }}
  value={range}
  onChange={setRange}
/>

Restricting selection

isDateUnavailable, min, and max work exactly as they do on Calendar — see its Restricting selection section for the full set of composable predicate helpers (disablePast(), disableWeekends(), beforeDate(d), anyOf(...), …), re-exported from this same @42/ui-react/date-picker barrel.

import { DatePicker, disablePast, disableWeekends, anyOf } from '@42/ui-react/date-picker';

<DatePicker isDateUnavailable={anyOf(disablePast(), disableWeekends())} />

Fixed height

Same as CalendarfixedWeeks always renders 6 week rows (padding with the adjacent month's days) instead of however many the visible month(s) actually span (4–6), keeping the popover's height constant while paging between months.

<DatePicker fixedWeeks />

Aliases

Named presets pinning range/withTime — the same component, a narrowed value type at the call site. See DatePicker's full API below; nothing about them is component-specific beyond which flags they pin.

  • DateRangePicker === <DatePicker range />
  • DateTimePicker === <DatePicker withTime />
  • DateTimeRangePicker === <DatePicker range withTime />

Forms

name wires a hidden <input type="hidden"> into native form submission — ${name}Start / ${name}End under range — serialized as a plain YYYY-MM-DD (or YYYY-MM-DDTHH:mm:ss under withTime), read directly off the machine's own value.

<DatePicker name="dueDate" />
<DateRangePicker name="period" />

Using it as a DataTable date filter

DataTable's date / date-range column filters (meta.filter) render this exact popover content — the calendar grid, presets rail, and (under withTime) time row — composed directly from DatePicker's exported parts, not a separate implementation:

// data-table-filter/data-table-date-filter.tsx (simplified)
<DatePicker.Root
  inline // the header cell's own Popover already owns the trigger/visibility
  selectionMode="range" // both `date` and `date-range` are range-shaped UIs — see below
  locale={locale}
  timeZone={timeZone}
  value={values} // parsed straight from the stored string bounds, see below
  onValueChange={(details) => commit(details.value)}
>
  <DatePicker.Context>
    {(api) => (
      <DatePicker.Content>
        {presets && <DatePicker.Presets presets={presets} range timeZone={timeZone} />}
        <DatePicker.Calendar numOfMonths={2} size="sm" timeZone={timeZone} />
        {withTime && (
          <DatePicker.TimePanelRow
            value={api.value[0]}
            onSetTime={(time) => api.setTime(time, 0)}
            hourCycle={hourCycle}
          />
        )}
      </DatePicker.Content>
    )}
  </DatePicker.Context>
</DatePicker.Root>

.Context (Ark's own context accessor) and .TimePanelRow (the bridge between TimeRow's display-space props and api.setTime/api.value, distinct from the dumber .TimePanel = TimeRow) are exported from this module specifically for this composition. The bridge stays entirely in Ark's own DateValue space — the filter's stored "YYYY-MM-DD" / "YYYY-MM-DDTHH:mm:ss" strings convert via @internationalized/date's parseDate/parseDateTime inbound and this module's own exported serializeDateValue outbound, never round-tripping through a native Date.

A calendar's range clicks are ordered (first click is the start, second the end) — a lone click derives { op: "gte" }; unlike the column's old two-independent-inputs UI, there's no interaction that produces an upper-bound-only { op: "lte" } value through this popover (the operator itself is unaffected and still fully settable programmatically via controlled columnFilters). See DataTable's own Date and date-range section for the full column-facing API, including the optional presets field.

API

Prop

Type

Accessibility

  • The trigger is a real <button> with aria-haspopup="grid" and aria-expanded, all from Ark.
  • The popover grid inherits every keyboard/ARIA behavior Calendar has — arrow-key navigation, aria-disabled on unavailable days, aria-selected.
  • The embedded time row's HOUR/MINUTE fields and AM/PM toggle carry real accessible names, same as standalone TimeInput — visually hidden (sr-only) with a decorative h/m/s suffix shown in their place, not removed.
  • Cancel/Apply (under withConfirm) are real <button>s with accessible names.
  • The portaled popover re-stamps data-color so the palette survives being rendered outside the trigger's own DOM subtree.

On this page