PixelDateRangePicker

Accessible date range picker with one or two-month grid, hover preview, presets, and min/max constraints.

  • Controlled — value + onChange (React), v-model (Vue), [(value)] or forms (Angular) — or uncontrolled with defaultValue
  • One or two-month calendar with hover preview while picking
  • Auto-swap of from/to when the second pick precedes the first
  • Optional quick-select presets and clearable trigger
  • Surface-aware styling with FieldShell label/hint/error wiring
Status
stable
Category
forms
Since
v1.9.0

API

import { PixelDateRangePicker } from '@pxlkit/ui-kit';
PropTypeDefaultDescription
valueDateRangeValue—The range; leave unset for an uncontrolled picker.
defaultValueDateRangeValue—Initial range while uncontrolled.
onChange(next: DateRangeValue) => void—Called with the range after every change: its start alone after a first pick, {} once cleared.
minDate—First day that can be picked.
maxDate—Last day that can be picked.
presets{ label: string; value: { from: Date; to: Date } }[]—Quick picks of whole ranges, shown above the months.
numberOfMonths1 | 22Months shown side by side.
surface'pixel' | 'linear'—Surface override; defaults to the nearest provider.
size'sm' | 'md' | 'lg''md'Trigger height.
labelstring—Label rendered above the trigger.
hintstring—Helper text below the field; hidden while error is set.
errorstring—Error message below the field; marks the trigger invalid.
placeholderstring'Select date range'Text shown while no range is picked.
clearablebooleanfalseAdds a clear button over the trigger's end and a Clear button under the months while a range is set.
namestring—Form field name — hidden inputs submit name.from and name.to as YYYY-MM-DD.
idstring—id of the trigger; generated when left out.
data-testidstring—data-testid of the trigger.

ref points to <button>.

Accessibility

WCAG target: 2.1 AA

  • two-month grid (role=grid)
  • aria-label on day cells
  • start/end announced
  • presets keyboard-reachable
  • clear button beside the trigger

Opening moves focus to the range start, else today; Escape, a preset and picking the end return it to the trigger. The popover is a dialog named "Choose date range". Each calendar panel uses role=grid with a labelled aria-live month header; day cells expose aria-selected for range edges, aria-current="date" for today and aria-disabled for out-of-bound days, and one day of the two months is in the tab order. Presets render as native buttons reachable via Tab. With clearable and a range set, a native "Clear range" button lies over the end of the trigger, beside it rather than inside (a button cannot contain a button): it is the next tab stop, clears the range, closes an open popover and moves focus to the trigger.

Keyboard

KeyActionWhen
ArrowLeftMove focus to the previous day—
ArrowRightMove focus to the next day—
ArrowUpMove focus to the previous week—
ArrowDownMove focus to the next week—
HomeMove focus to the first day of the week—
EndMove focus to the last day of the week—
PageUpMove focus to the same day of the previous month (its last day when shorter)—
PageDownMove focus to the same day of the next month (its last day when shorter)—
Shift+PageUpMove focus to the same day of the previous year—
Shift+PageDownMove focus to the same day of the next year—
EnterSelect the focused day (start, then end)—
SpaceSelect the focused day (start, then end)—
TabMove from the trigger to its clear button while a range is setclearable
Enter / SpaceClear the range and focus the trigger, without opening the popoverclear button focused

Usage

import { useState } from 'react';
import { PixelDateRangePicker, type DateRangeValue } from '@pxlkit/ui-kit';

export function Default() {
  const [range, setRange] = useState<DateRangeValue>({});
  return (
    <PixelDateRangePicker
      label="Date range"
      value={range}
      onChange={setRange}
      placeholder="Select date range"
    />
  );
}

Examples

Default

The calendar renders when the popover opens, in the browser: a server render holds the trigger alone, so no current month or today's mark can differ from the browser's.

import { useState } from 'react';
import { PixelDateRangePicker, type DateRangeValue } from '@pxlkit/ui-kit';

export function Default() {
  const [range, setRange] = useState<DateRangeValue>({});
  return (
    <PixelDateRangePicker
      label="Date range"
      value={range}
      onChange={setRange}
      placeholder="Select date range"
    />
  );
}

With Presets

import { useState } from 'react';
import { PixelDateRangePicker, type DateRangeValue } from '@pxlkit/ui-kit';

export function WithPresets() {
  const [range, setRange] = useState<DateRangeValue>({});
  const today = new Date();
  const start = new Date(today.getFullYear(), today.getMonth(), today.getDate());
  const last7 = new Date(today.getFullYear(), today.getMonth(), today.getDate() - 6);
  const last30 = new Date(today.getFullYear(), today.getMonth(), today.getDate() - 29);
  const next7 = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 7);
  return (
    <PixelDateRangePicker
      label="Reporting period"
      value={range}
      onChange={setRange}
      clearable
      presets={[
        { label: 'Last 7 days', value: { from: last7, to: start } },
        { label: 'Last 30 days', value: { from: last30, to: start } },
        { label: 'Next 7 days', value: { from: start, to: next7 } },
      ]}
    />
  );
}

Single Month

import { useState } from 'react';
import { PixelDateRangePicker, type DateRangeValue } from '@pxlkit/ui-kit';

export function SingleMonth() {
  const [range, setRange] = useState<DateRangeValue>({});
  return (
    <PixelDateRangePicker
      label="Single-month view"
      hint="Compact one-month calendar"
      value={range}
      onChange={setRange}
      numberOfMonths={1}
      clearable
    />
  );
}

Related