<!-- Protocore Design System — DatePicker -->
# DatePicker

- **Category:** Inputs (`inputs`)
- **Slug:** `inputs/date-picker`
- **Status:** stable
- **Platforms:** web
- **Import:** `import { DatePicker } from "@protocore/pds";`
- **Docs:** https://pds.protocore.io/components/inputs/date-picker

> Sunken field showing a formatted date that opens a Calendar in a raised overlay for point-and-click selection.

## When to use it

Use **DatePicker** for choosing a single date visually — a delivery date, a publish date — where seeing the surrounding month helps. When the field must be keyboard- and screen-reader-first (or filled fast from memory), pair it with, or swap it for, **DateInput**. For a start/end window use **DateRangePicker**. When the grid should stay inline rather than behind a field, use **Calendar** directly.

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `className` | `string` | no | — | — |
| `clearable` | `boolean` | no | `false` | Show a clear button when a date is selected. |
| `defaultOpen` | `boolean` | no | — | Initial open state in uncontrolled mode. |
| `defaultValue` | `Date \| null` | no | — | Initial selected date in uncontrolled mode. |
| `disabled` | `boolean` | no | — | Disable the whole control. |
| `format` | `DateTimeFormatOptions \| ((date: Date) => string)` | no | ``{ dateStyle: "medium" }`` | How the selected date renders in the field. Pass `Intl.DateTimeFormatOptions` or a formatter function. |
| `invalid` | `boolean` | no | — | Mark the field invalid — danger border plus `aria-invalid`. |
| `isDateDisabled` | `((date: Date) => boolean)` | no | — | Predicate to disable arbitrary days. |
| `locale` | `string` | no | — | BCP-47 locale for the field label and the calendar. |
| `max` | `Date` | no | — | Latest selectable day (inclusive). |
| `min` | `Date` | no | — | Earliest selectable day (inclusive). |
| `onOpenChange` | `((open: boolean) => void)` | no | — | Fires when the overlay opens or closes. |
| `onValueChange` | `((date: Date \| null) => void)` | no | — | Fires with the chosen date (or `null` when cleared). |
| `open` | `boolean` | no | — | Controlled open state of the overlay. |
| `placeholder` | `string` | no | `Select date` | Text shown when no date is selected. |
| `size` | `enum` | no | `md` | Field height: `sm` (32) · `md` (36, default) · `lg` (40). |
| `style` | `CSSProperties` | no | — | — |
| `value` | `Date \| null` | no | — | Controlled selected date. Pass `null` for no selection. |
| `weekStartsOn` | `enum` | no | `1` | First column of the week: 0 = Sunday … 6 = Saturday. |

## Examples

### Basics

An Input-styled trigger shows the selected date (formatted with Intl.DateTimeFormat) and a calendar glyph; clicking opens the month grid in a popover. Controllable via `value` / `defaultValue` / `onValueChange`.

```tsx
import { useState } from "react";
import { DatePicker } from "@protocore/pds";

export default function Basics() {
  // Sunken trigger shows the formatted date; clicking opens the calendar.
  const [value, setValue] = useState<Date | null>(new Date(2026, 6, 6));

  return (
    <div style={{ width: 260 }}>
      <DatePicker aria-label="Publish date" value={value} onValueChange={setValue} locale="en-GB" />
    </div>
  );
}
```

### In a Field, bounded & clearable

Wrap it in a `Field` for a label and hint. Constrain selection with `min` / `max`, and set `clearable` to expose a reset affordance once a date is chosen.

```tsx
import { useState } from "react";
import { DatePicker, Field } from "@protocore/pds";

export default function InField() {
  // Bounded to the next 60 days and clearable — a typical "schedule a deploy"
  // field with a label and hint from Field.
  const today = new Date(2026, 6, 6);
  const max = new Date(2026, 8, 4);
  const [value, setValue] = useState<Date | null>(null);

  return (
    <div style={{ width: 280 }}>
      <Field label="Scheduled deploy" htmlFor="deploy-date" hint="Within the next 60 days.">
        <DatePicker
          id="deploy-date"
          value={value}
          onValueChange={setValue}
          min={today}
          max={max}
          defaultValue={today}
          clearable
          locale="en-GB"
        />
      </Field>
    </div>
  );
}
```

## Do & don't

**Do**

- Give the trigger an accessible name via a `Field` label or `aria-label`.
- Set `min` / `max` to the valid window so out-of-range days can't be picked.
- Offer `DateInput` alongside it for keyboard-first users entering known dates.
- Pass a `locale` so the formatted value matches the rest of the UI.

**Don't**

- Don't rely on the picker alone for a distant date — a birthday is faster to type.
- Don't leave the trigger unlabelled when no visible field label wraps it.
- Don't reformat the value yourself in state — read the `Date` from `onValueChange`.
- Don't nest it inside another popover trigger where the overlays would fight for focus.

## Accessibility

**Keyboard**

| Keys | Action |
| --- | --- |
| `Enter / Space` | Open the calendar from the focused trigger. |
| `Arrow keys` | Move between days once the calendar is open. |
| `Enter` | Select the focused day and close the overlay. |
| `Esc` | Close the overlay without changing the value. |

**Notes**

- The overlay is a Radix Popover: it is portalled, dismisses on outside-click and Escape, and returns focus to the trigger on close.
- Selecting a day closes the overlay and moves focus back to the trigger, so keyboard flow continues in place.
- `invalid` sets `aria-invalid` on the trigger; the trigger exposes `aria-expanded` for its open state.

## Related

`calendar`, `date-input`, `date-range-picker`, `field`

---

© Protocore. All rights reserved. Use of the Protocore Design System requires prior written authorization from Protocore (contact@protocore.io). These machine-readable materials must not be ingested into ML-training datasets or derivative design systems. See https://pds.protocore.io/legal/
