# Date picker

Package: @commenda/pathfinder@0.1.0
Page: /docs/components/date-picker
Figma: https://www.figma.com/design/dn7BkGYEfIyP0o2zfesfVh/The-System?node-id=188-2

```tsx
import { DatePicker } from '@commenda/pathfinder/date-picker';

export const DatePickerDefault = () => (
  <DatePicker label="Filing date" format="us" clearConfig={{}} />
);
```


## Installation [#installation]

Import the component in your screen:

```tsx
import { DatePicker } from '@commenda/pathfinder/date-picker';
```

Load the stylesheet once in your app root, after host styles:

```tsx
import '@commenda/pathfinder/styles.css';
```

Pathfinder ships precompiled CSS; no Tailwind setup is required. In Next.js App Router, put interactive state and callbacks in a client component. See [installation](/docs/getting-started/installation) for the workspace setup.

## API reference [#api-reference]

| Prop | Type | Requirement | Behavior |
| --- | --- | --- | --- |
| `mode` | `"single" \| "range" \| "month"` | conditional | `'single'`; discriminates value and constraints |
| `value` | `string \| null \| DateRange` | conditional | Single `YYYY-MM-DD`, month `YYYY-MM`, range `{ start, end }` of date strings/null |
| `defaultValue` | `string \| null \| DateRange` | optional | Single `YYYY-MM-DD`, month `YYYY-MM`, range `{ start, end }` of date strings/null |
| `onValueChange` | `((value: string \| null) => void) \| ((value: DateRange) => void)` | conditional | Controlled mode requires it. Receives valid values, explicit clears, or partial ranges |
| `format` | `"us" \| "in" \| "iso"` | optional | `'iso'`; MM/DD/YYYY, DD/MM/YYYY, YYYY-MM-DD |
| `calendarConfig` | `false \| { weekStartsOn?: 0 \| 1; }` | optional | Calendar enabled, Sunday first; false is typed-only |
| `constraintConfig` | `DateConstraints \| MonthConstraints` | optional | Single/range date constraints; month mode uses `isMonthUnavailable` and month strings |
| `presetConfig` | `{ items: readonly { id: string; label: string; value: DateRange; }[]; }` | optional | Range-only; presets obey the same constraints |
| `clearConfig` | `{ label?: string; }` | optional | Optional clear action |
| `name` | `string` | optional | Single/month submit one ISO value; range submits `name.start` and `name.end` |
| `form` | `string` | optional | Single/month submit one ISO value; range submits `name.start` and `name.end` |
| `label` | `string` | conditional | Visible field label. Supply an accessible name when it is omitted. |
| `hideLabel` | `boolean` | optional | Visually hide the label while preserving its accessible name. |
| `aria-label` | `string` | conditional | Defines a string value that labels the current element. |
| `required` | `boolean` | conditional | Mark the field as required. See validation guidance for the component. |
| `labelConfig` | `{ optionalLabel: string; help?: never; } \| { help: string; optionalLabel?: never; }` | optional | Choose either `optionalLabel` or `help`, never both. `never` excludes the other field. |
| `sourceConfig` | `FieldSourceConfig` | optional |  |
| `description` | `string` | optional | Supporting guidance associated with the field. |
| `errorConfig` | `{ message: string; reveal?: "after-blur" \| "immediate"; }` | optional | Validation message and when it becomes visible. |
| `disableConfig` | `DisableConfig` | optional | Disabled state with an optional explanation. |
| `id` | `string` | optional | Stable HTML identifier for the component. |
| `className` | `string` | optional | Additional classes for the component root. |
| `readOnly` | `boolean` | optional | Allow reading the current value without editing it. |
| `onBlur` | `FocusEventHandler<HTMLDivElement>` | optional | Called when focus leaves the component. |
| `onFocus` | `FocusEventHandler<HTMLDivElement>` | optional | Called when focus enters the component. |

### value fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `start` | `string \| null` | Yes |  |
| `end` | `string \| null` | Yes |  |

### defaultValue fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `start` | `string \| null` | Yes |  |
| `end` | `string \| null` | Yes |  |

### calendarConfig fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `weekStartsOn` | `0 \| 1` | No |  |

### constraintConfig fields

Choose one shape; excluded fields cannot be combined.

#### Option 1

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `min` | `string` | No |  |
| `max` | `string` | No |  |
| `isDateUnavailable` | `((date: string) => boolean)` | No |  |

#### Option 2

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `min` | `string` | No |  |
| `max` | `string` | No |  |
| `isMonthUnavailable` | `((month: string) => boolean)` | No |  |

### presetConfig fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `items` | `readonly { id: string; label: string; value: DateRange; }[]` | Yes |  |

### clearConfig fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `label` | `string` | No |  |

### labelConfig fields

Choose one shape; excluded fields cannot be combined.

#### Option 1

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `optionalLabel` | `string` | Yes |  |

Do not supply: `help`.

#### Option 2

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `help` | `string` | Yes |  |

Do not supply: `optionalLabel`.

### sourceConfig fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `kind` | `"entity" \| "record" \| "document"` | Yes |  |
| `label` | `string` | Yes |  |
| `preview` | `ReactNode` | Yes |  |
| `onOpen` | `() => void` | Yes |  |
| `side` | `"top" \| "bottom"` | No |  |

### errorConfig fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `message` | `string` | Yes |  |
| `reveal` | `"after-blur" \| "immediate"` | No |  |

### disableConfig fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `isDisabled` | `boolean` | Yes |  |
| `reason` | `string` | No |  |

Shared presentation and `readOnly` follow [Input](/docs/components/input). Refs and native form behavior differ because this is a composite editor. Set valid ISO strings in controlled props; invalid or incomplete drafts remain in the editor and block native form submission. They never silently revert on blur. A changed external value replaces the draft; form reset restores uncontrolled defaults and clears draft edits.

## Typing and calendar selection [#typing-and-calendar-selection]

The parent field uses a segmented date editor: type or adjust its day, month, and year segments directly. The calendar icon opens the matching day, range, or month picker. Choosing a date updates those same segments and the same ISO value; neither interaction owns a second date state. Invalid drafts stay visible until corrected.

## Range and presets [#range-and-presets]


```tsx
import { DatePicker } from '@commenda/pathfinder/date-picker';

export const DatePickerRange = () => (
  <DatePicker
    mode="range"
    label="Reporting period"
    defaultValue={{ start: '2026-10-01', end: '2026-10-31' }}
    presetConfig={{
      items: [
        {
          id: 'october',
          label: 'October 2026',
          value: { start: '2026-10-01', end: '2026-10-31' },
        },
        {
          id: 'quarter',
          label: 'Fourth quarter',
          value: { start: '2026-10-01', end: '2026-12-31' },
        },
      ],
    }}
    clearConfig={{}}
  />
);
```


Click a day to start a new range; the calendar stays open while you choose its end. The second click completes the range, including a same-day range, and closes the calendar. Choosing an earlier second day orders the two dates chronologically. End must be on or after start. A range cannot cross an unavailable date. Wide screens show two months; narrow screens show one navigable month. For large selectable periods, provide practical application min/max bounds so predicate checks stay inexpensive.

## Month [#month]


```tsx
import { DatePicker } from '@commenda/pathfinder/date-picker';

export const DatePickerMonth = () => (
  <DatePicker
    mode="month"
    label="Accounting month"
    defaultValue="2026-10"
    clearConfig={{}}
  />
);
```


## Typed-only [#typed-only]


```tsx
import { DatePicker } from '@commenda/pathfinder/date-picker';

export const DatePickerTyped = () => (
  <DatePicker
    label="Date of incorporation"
    format="in"
    calendarConfig={false}
    description="Type DD/MM/YYYY. Arrow keys navigate and adjust segments."
  />
);
```


Each segment has an accessible name. Arrow keys move between or adjust segments. Dates are calendar strings; do not convert through `new Date('YYYY-MM-DD').toISOString()` in application adapters.

## Constraints [#constraints]


```tsx
import { DatePicker } from '@commenda/pathfinder/date-picker';

export const DatePickerConstraints = () => (
  <DatePicker
    label="Available filing date"
    defaultValue="2026-10-01"
    constraintConfig={{
      min: '2026-10-01',
      max: '2026-10-31',
      isDateUnavailable: (date) => date === '2026-10-15',
    }}
    description="October only; the 15th is unavailable. Typed dates use the same constraints."
  />
);
```


## States [#states]


```tsx
import { DatePicker } from '@commenda/pathfinder/date-picker';

export const DatePickerStates = () => (
  <div style={{ display: 'grid', gap: 20 }}>
    <DatePicker label="Read-only date" defaultValue="2026-10-12" readOnly />
    <DatePicker
      label="Disabled date"
      defaultValue="2026-10-12"
      disableConfig={{ isDisabled: true }}
    />
    <DatePicker
      label="Required date"
      required
      errorConfig={{ message: 'Choose a filing date.', reveal: 'immediate' }}
    />
  </div>
);
```


Read [composite field rules](/docs/components/date-picker#design-rules).

## Design rules [#design-rules]

The calendar follows [Calendar](https://www.figma.com/design/dn7BkGYEfIyP0o2zfesfVh/The-System?node-id=190-587), [day cells](https://www.figma.com/design/dn7BkGYEfIyP0o2zfesfVh/The-System?node-id=188-33), and [month cells](https://www.figma.com/design/dn7BkGYEfIyP0o2zfesfVh/The-System?node-id=188-50).

- The typed block uses the approved segmented-field spacing: 40px minimum height, 8px horizontal padding, 24px-tall segments, 40px year and 28px month/day widths, 2px gaps, and 2px separator padding. This user-requested spacing overrides Figma's compact 28px field. The 16px calendar icon sits in a 24px action target.
- The floating card has a 10px radius, pearl-300 hairline, menu shadow, and 12px calendar padding.
- Navigation is 24px; weekday labels are 24px tall; days are 32px square with 2px between weeks. Month options are 72 × 32px.
- Selected dates and range ends use blue-700. Interior dates form a continuous blue-100 band. Today has a 4px dot. Presets show a check when their range matches.
- Outside days and weekday labels use pearl-800 for readable contrast, a deliberate adjustment from Figma's lighter colors.
- At narrow widths the range calendar shows one month and puts supplied presets above the calendar. Preset dates and labels remain application-owned.

### Reference rules [#reference-rules]

Figma guidance and reviewed implementation decisions for this component. Rule IDs remain stable for source comments and AI references.

### Dates [#dates]

- **DATE-001:** Single, range, month, and typed-only use the approved segmented-field spacing: minimum height 40px, horizontal inset 8px, fixed 40px year and 28px month/day segments, 24px segment height, 2px segment gaps and separator padding. Locale segment order is explicit through `format`; default is ISO.
- **DATE-002:** Calendar 190:587, day cell 188:33, and month cell 188:50: offset 4px, radius 10, pearl-300 hairline, 12px padding, 24px navigation and weekday row, 32px days with 2px week gaps, 72 × 32px month options. Sunday first; selected blue-700, continuous blue-100 range band, 4px today dot, unavailable dates disabled and muted. These exact cell frames supersede the older page note about strikethrough. Supplied presets have a divided sidebar and selection check; narrow screens place presets above one month. Weekday/outside text is strengthened to pearl-800 for contrast.
- **DATE-003:** The private DateField uses segmented editing and keyboard navigation with Pathfinder tokens. Keep incomplete/invalid drafts visible; never replace them with the old committed value on blur.
- **DATE-004:** ISO date-only strings, no UTC conversion. Typed input, calendar, and presets use identical constraints. Typed ranges reject reversed dates and ranges crossing unavailable days. Calendar selection uses two clicks: the first starts a new range and keeps the popup open; the second completes it (including the same day). Month mode uses month predicates.
- **DATE-005:** `calendarConfig={false}` removes the icon/popover. Composite refs target the group. Native forms submit ISO values; invalid drafts cannot submit an older saved value.

- **DATE-006:** First calendar click starts a fresh range and keeps the popup open. Second click completes it, including a same-day range. Display a prompt for the pending end date. Selected days retain their blue fill on hover.

### Explicit differences and boundaries [#explicit-differences-and-boundaries]

- **Editors:** Select and DatePicker retain their existing control heights; they are not pixel-identical to compact picker drawings. Text/number editors use the compact geometry. Async save errors remain visibly actionable with retry.

### Shared rules [#shared-rules]

Labels activate the first input; every date segment has its own accessible label. Descriptions, disabled reasons, and validation messages stay associated with the field. Error icons center on the first 18px text line, including wrapped messages. Controlled values require callbacks; uncontrolled form resets restore defaults. Public props expose application values, never private primitive or calendar objects. Styles ship precompiled for React 18/19 and Next.js consumers.

Date segments use native text selection as their single editing highlight. Focusing a segment selects its text for replacement; the field retains its own outer focus or validation border.
