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

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

> Sunken field that opens a two-month Calendar for picking a start/end range, with live hover preview and auto-close.

## When to use it

Use **DateRangePicker** for a start/end window — a report period, a booking stay, a campaign flight. The two-month view makes cross-month ranges (e.g. a week spanning the 28th to the 3rd) selectable without paging. For a single date use **DatePicker**; for a keyboard-first typed entry use **DateInput**; when the grid should stay inline, use **Calendar** with the `range` prop directly.

## Props

_No documented props._

## Examples

### Basics

The trigger shows the formatted range; clicking opens two months side by side. The first click sets the start, hovering previews the range, and the second click sets the end and closes the overlay. Value is `{ start, end }`.

```tsx
import { useState } from "react";
import { DateRangePicker, type DateRange } from "@protocore/pds";

export default function Basics() {
  // Two-month overlay; first click sets start, second sets end and closes.
  const [range, setRange] = useState<DateRange>({
    start: new Date(2026, 6, 6),
    end: new Date(2026, 6, 12),
  });

  return (
    <div style={{ width: 300 }}>
      <DateRangePicker
        aria-label="Report window"
        value={range}
        onValueChange={setRange}
        locale="en-GB"
      />
    </div>
  );
}
```

### In a Field, bounded & clearable

Wrap it in a `Field` for a label. Constrain the window with `min` / `max`, and set `clearable` to reset the range. The range is always emitted ordered (`start` ≤ `end`).

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

export default function InField() {
  // A billing period, bounded to this year and clearable.
  const [range, setRange] = useState<DateRange>({ start: null, end: null });

  const summary =
    range.start && range.end
      ? `${range.start.toLocaleDateString("en-GB")} – ${range.end.toLocaleDateString("en-GB")}`
      : "No period selected";

  return (
    <div style={{ width: 320 }}>
      <Field label="Billing period" htmlFor="billing-range" hint="Start and end are inclusive.">
        <DateRangePicker
          id="billing-range"
          value={range}
          onValueChange={setRange}
          min={new Date(2026, 0, 1)}
          max={new Date(2026, 11, 31)}
          defaultValue={{ start: new Date(2026, 6, 1), end: null }}
          clearable
          locale="en-GB"
        />
      </Field>
      <p style={{ font: "13px system-ui", opacity: 0.7, marginTop: 8 }}>{summary}</p>
    </div>
  );
}
```

## Do & don't

**Do**

- Give the trigger an accessible name via a `Field` label or `aria-label`.
- Keep the default two-month layout for ranges that commonly cross a month boundary.
- Set `min` / `max` to the bookable window so invalid ranges can't be formed.
- Read the ordered `{ start, end }` from `onValueChange` rather than tracking clicks yourself.

**Don't**

- Don't use it for a single date — a DatePicker reads as one value, not a window.
- Don't assume the first click is always the earlier date; the component orders the range for you.
- Don't leave the trigger unlabelled when no visible field label wraps it.
- Don't shrink to one month if your ranges routinely span a boundary — paging hurts.

## Accessibility

**Keyboard**

| Keys | Action |
| --- | --- |
| `Enter / Space` | Open the calendar from the focused trigger. |
| `Arrow keys` | Move between days across both months once open. |
| `Enter` | Set the start, then the end; the second selection closes the overlay. |
| `Esc` | Close the overlay without changing the range. |

**Notes**

- The overlay is a Radix Popover — portalled, dismissed on outside-click and Escape, and it restores focus to the trigger on close.
- Each month is its own `role="grid"` with a caption; the range endpoints carry `aria-selected`.
- Hover preview is a pointer nicety layered on top of full keyboard selection — it is never the only way to see the pending range.

## Related

`date-picker`, `calendar`, `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/
