Source: https://monarchui.dev/components/date-picker/

Components · Date, time and scheduling

# DatePicker

A date field: type in the locale’s format or pick from a calendar popover, with presets, min/max and Field errors.

@rookcom/monarch-react/scheduling

Component

## Due date with quick picks

## Usage examples

### Min, max and an error

### Sizes and formats

### Inside a dialog

## Purpose

Lets people enter one calendar date in a form, by typing it the way their locale writes dates or by picking it from a calendar. The value is an ISO `YYYY-MM-DD` string with no time zone, so it never shifts by a day. Replaces native `type="date"` inputs, which render differently in every browser and cannot show presets. Import from `@rookcom/monarch-react/scheduling`.

### Use when

- Due dates, start dates, birthdays, renewal and expiry dates in forms and editors.
- A date with quick picks such as Today, Tomorrow and In a week.

### Avoid when

- A date and a time: use DateTimePicker.
- A period: use DateRangePicker.
- Showing a date read-only: format it with RelativeTime or plain text.

## Design principles

- Typing is first-class: the field accepts the locale’s numeric order, month names, ISO and digits without separators.
- Invalid text is kept so it can be fixed, and explained with the expected format; the value only changes to a valid date.
- The calendar is the same MiniMonth grid everywhere, with the same keyboard model.
- No time zones: a date is a calendar date.

## API reference

```tsx
import { DatePicker } from '@rookcom/monarch-react/scheduling';
```

| Export | Kind | Rendering | Root class |
| --- | --- | --- | --- |
| `DatePicker` | component | client | `.mn-date-picker` |

The ref goes to the text `<input>`; native input props (`id`, `aria-*`, `onBlur`, `autoFocus`, …) go there too. `className` styles the wrapper.

With `name`, a hidden input submits the ISO value; the visible input has no name.

Generated from the TypeScript declarations of `packages/react/src/components/scheduling/date-picker`.

## Properties

`value`/`onValueChange` (controlled) or `defaultValue`; `onValueChange` receives a valid ISO date or `null`, never invalid text.

`presets`: `{ label, days }` relative to today or `{ label, date }` fixed.

`onValidationChange` reports `invalid`, `before-min`, `after-max`, `unavailable` or `null`; set `showErrors={false}` to render your own FieldError.

`messages` translates every string, including the error texts.

### `DatePicker`

A date field: type a date in the locale's format, or pick it from a calendar popover with optional quick picks. The value is an ISO `YYYY-MM-DD` string with no time or time zone. Validates min, max and disabled dates, and reports errors through Field.

| Prop | Type | Description |
| --- | --- | --- |
| `children` | `ReactNode` |  |
| `className` | `string` | Class for the outer wrapper (the input itself is `.mn-date-picker__input`). |
| `clearable` | `boolean` | Show a clear button while there is a value. Default true. |
| `defaultValue` | `string \| null` | Initial date (uncontrolled). |
| `disabled` | `boolean` |  |
| `formatOptions` | `Intl.DateTimeFormatOptions` | Display format of the text. Default: the locale's numeric date (`04/11/2026`). |
| `getDayInfo` | `((date: IsoDate) => MiniMonthDayInfo \| undefined)` | Dots and descriptions for calendar days (see MiniMonth). |
| `id` | `string` |  |
| `invalid` | `boolean` | Marks the field invalid. Inside a Field, the Field's `invalid` is used. Built-in errors also set it. |
| `isDateDisabled` | `((date: IsoDate) => boolean)` | Blocks extra dates (weekends, holidays). |
| `locale` | `string` | BCP 47 locale for typing, display and the calendar. Default: the runtime's. Pass it for server rendering. |
| `max` | `string` | Latest allowed date. |
| `messages` | `Partial<DatePickerMessages>` | Text for translation. |
| `min` | `string` | Earliest allowed date. Earlier typed dates show an error; earlier days are disabled. |
| `onOpenChange` | `((open: boolean) => void)` | Called when the calendar opens or closes. |
| `onValidationChange` | `((error: DateValidationError \| null) => void)` | Called when the built-in validation result changes (`null` when valid). |
| `onValueChange` | `((value: IsoDate \| null) => void)` | Called with a new valid date, or `null` when cleared. Never called with invalid text. |
| `open` | `boolean` | Whether the calendar is open (controlled). |
| `presets` | `readonly DatePickerPreset[]` | Quick picks such as Today and Tomorrow, shown beside the calendar. |
| `showErrors` | `boolean` | Render the built-in error message under the field. Default true. Turn off to show your own FieldError. |
| `showWeekNumbers` | `boolean` | Show ISO week numbers in the calendar. |
| `size` | `"sm" \| "md" \| "lg"` | Control height. Default `md`. |
| `today` | `string` | Today's date. Default: the device's. |
| `value` | `string \| null` | The date (controlled), `YYYY-MM-DD`, or `null` for none. |
| `weekStartsOn` | `Weekday` | First day of the calendar week, 0 (Sunday) to 6. Default: from the locale. |

## Variants

| Prop | Values | Description |
| --- | --- | --- |
| `size` | `'sm'``'md'``'lg'` | Control height, as Input. Default `md`. |
| `presets` | `'none'``'list'` | Quick picks beside the calendar (above it on phones). |
| `clearable` | `'true'``'false'` | A clear button while there is a value. Default true. |
| `formatOptions` | `'numeric (default)'``'Intl options'` | How the value is written in the field, e.g. `{ dateStyle: "medium" }`. |

## States

| State | Exposed as | Behaviour |
| --- | --- | --- |
| Empty | – | Shows the format hint as placeholder, e.g. DD/MM/YYYY. |
| Open | `data-state="open" on the calendar button` | The calendar popover is showing. |
| Invalid | `aria-invalid="true", data-invalid` | Unreadable text, a date outside min/max or a disabled date; a message explains why. |
| Disabled | `disabled, data-disabled` | From the prop or the Field; the calendar cannot open. |
| Read-only | `readonly` | The value can be read and copied; the calendar button is disabled. |

## Interaction behaviour

- Typed text is read on blur and on Enter. A valid date is re-formatted in the locale; anything else stays as typed with an error.
- The calendar button or ↓ in the field opens the calendar, focused on the value (or today). Picking a day or a preset closes it and returns focus to the field.
- Emptying the field or pressing the clear button sets the value to `null`.
- The calendar is a modal popover: it traps focus while open and works inside Dialog and Sheet; Escape closes only the calendar.

## Keyboard support

| Keys | Action |
| --- | --- |
| Enter (in the field) | Read the typed date. |
| ↓ (in the field) | Open the calendar. |
| Arrows, Home/End, Page Up/Down | Move in the calendar (see MiniMonth). |
| Enter / Space (in the calendar) | Pick the focused day and close. |
| Escape | Close the calendar without changing the value. |
| Tab | Moves between the field, the clear and calendar buttons; inside the calendar, between presets, month buttons and the grid. |

## Accessibility

Role

textbox, with a button that opens a dialog containing a grid

Accessible name

From FieldLabel (or

aria-label

). The calendar button is named "Choose date" or "Change date, <full date>"; the popover "Choose date".

- The calendar button has `aria-haspopup="dialog"` and `aria-expanded`.
- Errors are rendered under the field, linked by `aria-describedby` and announced politely; the field gets `aria-invalid`.
- Inside a Field, `aria-required`, the description and `disabled` are wired automatically.
- The format hint is the placeholder and is repeated in the error text, so it is not the only cue.
- Presets are buttons with `aria-pressed` for the current value.

## Responsive behaviour

- The field fills its container; the popover never exceeds the viewport minus 1rem.
- Below 576px, presets move above the calendar as wrapping chips.
- Larger buttons and presets on touch screens.

## Theme support

- Uses Input’s tokens for the field and MiniMonth’s for the calendar, so every theme and more-contrast mode applies.
- Transitions are removed under `prefers-reduced-motion: reduce`.

## Do and don't

### Do

- Put it in a Field with a FieldLabel, and pass `locale` from the user’s settings.
- Store and send the ISO string as is; format it for display with Intl and `timeZone: "UTC"`.
- Offer presets for the common choices in task and deadline editors.

### Don't

- Do not build a `Date` from the value in local time (`new Date("2026-11-04")` is UTC midnight and shows the previous day west of UTC).
- Do not show overdue state only by colouring the field; add a Badge or text.
- Do not use it for a date with a time: use DateTimePicker.

## Related components

- [DateTimePicker](https://monarchui.dev/components/date-time-picker/): A date and a time.
- [DateRangePicker](https://monarchui.dev/components/date-range-picker/): A start and end date.
- [MiniMonth](https://monarchui.dev/components/mini-month/): The calendar grid on its own.
- [Field](https://monarchui.dev/components/form-field/): Label, description and errors.

## Dependencies

| Kind | Depends on |
| --- | --- |
| npm packages | `react` |
| Monarch components | [Field](https://monarchui.dev/components/form-field/), [Popover](https://monarchui.dev/components/popover/), [MiniMonth](https://monarchui.dev/components/mini-month/) |

The popover is Monarch Popover (Radix), opened as modal.

## Testing coverage

- Labelled textbox wired to Field (required, description); calendar button with `aria-haspopup`; axe clean.
- Typed dates in en-GB and en-US order, on blur and Enter; month names; re-formatting.
- Invalid text kept with an error and `aria-invalid`, value unchanged, `onValidationChange`.
- min, max and disabled dates rejected with messages naming the bound.
- Calendar opens focused on the value, picks with the keyboard, closes and returns focus; ↓ opens on the typed month; Escape.
- Presets relative to today, disabled outside the bounds.
- Clear button and emptying the text; controlled value; hidden input for forms; disabled and read-only.
- Inside Dialog: the calendar takes focus and Escape closes only the calendar.

| Check | Covered |
| --- | --- |
| Axe in jsdom | Yes |
| Server rendering | Yes |
| Browser suites | Visual baselines: /__gallery/date-picker and the open calendar (overlays/date-picker-*.png); Cross-browser axe: /components/date-picker |

Tests: `packages/react/src/components/scheduling/date-picker`

## All example code

### Due date with quick picks

```tsx
import { useState } from 'react';
import { Field, FieldLabel, FieldDescription } from '@rookcom/monarch-react';
import { DatePicker } from '@rookcom/monarch-react/scheduling';

const TODAY = '2026-11-04';

const PRESETS = [
  { label: 'Today', days: 0 },
  { label: 'Tomorrow', days: 1 },
  { label: 'In a week', days: 7 },
  { label: 'In two weeks', days: 14 },
];

function DueDate() {
  const [value, setValue] = useState<string | null>(null);
  return (
    <Field>
      <FieldLabel>Due date</FieldLabel>
      <DatePicker
        locale="en-GB"
        today={TODAY}
        presets={PRESETS}
        value={value}
        onValueChange={setValue}
      />
      <FieldDescription>Type a date or pick one. Value: {value ?? 'none'}</FieldDescription>
    </Field>
  );
}

<DueDate />
```

### Min, max and an error

```tsx
import { Field, FieldLabel } from '@rookcom/monarch-react';
import { DatePicker } from '@rookcom/monarch-react/scheduling';

const TODAY = '2026-11-04';

<Field>
  <FieldLabel>Renewal date</FieldLabel>
  <DatePicker
    locale="en-GB"
    today={TODAY}
    min="2026-11-01"
    max="2027-10-31"
    defaultValue="2027-12-01"
  />
</Field>
```

### Sizes and formats

```tsx
import { DatePicker } from '@rookcom/monarch-react/scheduling';

const TODAY = '2026-11-04';

<DatePicker
  aria-label="Start"
  size="sm"
  locale="en-US"
  today={TODAY}
  defaultValue="2026-11-04"
/>
<DatePicker
  aria-label="Published"
  size="lg"
  locale="de-DE"
  today={TODAY}
  defaultValue="2026-11-04"
  formatOptions={{ dateStyle: 'medium' }}
/>
<DatePicker
  aria-label="Locked"
  disabled
  locale="en-GB"
  today={TODAY}
  defaultValue="2026-11-04"
/>
```

### Inside a dialog

```tsx
import { Dialog, DialogContent, DialogTrigger, DialogTitle, DialogBody, Button, Field, FieldLabel } from '@rookcom/monarch-react';
import { DatePicker } from '@rookcom/monarch-react/scheduling';

const TODAY = '2026-11-04';

function InDialog() {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button>Schedule review</Button>
      </DialogTrigger>
      <DialogContent size="sm">
        <DialogTitle>Schedule review</DialogTitle>
        <DialogBody>
          <Field>
            <FieldLabel>Review date</FieldLabel>
            <DatePicker locale="en-GB" today={TODAY} min={TODAY} />
          </Field>
        </DialogBody>
      </DialogContent>
    </Dialog>
  );
}

<InDialog />
```
