PixelDatePicker

Accessible date input with popover calendar grid, keyboard navigation, presets, and min/max constraints.

  • Controlled — value + onChange (React), v-model (Vue), [(value)] or forms (Angular) — or uncontrolled with defaultValue
  • Popover calendar with roving tabindex and full keyboard navigation
  • Min/max bounds plus disabledDates (array or predicate)
  • Optional quick-select presets and clearable trigger
  • Surface-aware styling with FieldShell label/hint/error wiring
Status
stable
Category
forms
Since
v1.8.0

API

import { PixelDatePicker } from '@pxlkit/ui-kit';
PropTypeDefaultDescription
valueDate | null—The picked day, null for none; leave unset for an uncontrolled picker.
defaultValueDate—Initial day while uncontrolled.
onChange(date: Date | null) => void—Called with the day picked, at its start, or null once cleared.
minDate—First day that can be picked.
maxDate—Last day that can be picked.
disabledDatesDate[] | ((d: Date) => boolean)—Days that cannot be picked: a list, or a test of each day.
format(d: Date) => string—The trigger's text for the picked day; the locale's long date by default.
placeholderstring'Select date'Text shown while no day is picked.
clearablebooleanfalseShows a Clear button under the grid while a day is picked.
presets{ label: string; value: Date }[]—Quick picks shown above the grid.
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.
namestring—Form field name — a hidden input submits the day as YYYY-MM-DD.
idstring—id of the trigger; generated when left out.
data-testidstring—Hook for tests + custom triggers.

ref points to <button>.

Accessibility

WCAG target: 2.1 AA

  • input + popover calendar (role=grid)
  • aria-label on day cells
  • min/max enforced

Opening moves focus to the picked day, else today; Escape, Enter and a pick return it to the trigger. The popover is a dialog named "Choose date". Day cells expose aria-selected, aria-current="date" (today) and aria-disabled; the calendar grid uses role=grid with explicit role=row wrappers and an aria-live month label. Moves skip disabled days and stop at min/max.

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 and close the popover—
SpaceSelect the focused day and close the popover—

Usage

import { useState } from 'react';
import { PixelDatePicker } from '@pxlkit/ui-kit';

export function Default() {
  const [date, setDate] = useState<Date | null>(null);
  return (
    <PixelDatePicker
      label="Pick a date"
      value={date}
      onChange={setDate}
      placeholder="Select date"
    />
  );
}

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 { PixelDatePicker } from '@pxlkit/ui-kit';

export function Default() {
  const [date, setDate] = useState<Date | null>(null);
  return (
    <PixelDatePicker
      label="Pick a date"
      value={date}
      onChange={setDate}
      placeholder="Select date"
    />
  );
}

With Presets

import { useState } from 'react';
import { PixelDatePicker } from '@pxlkit/ui-kit';

export function WithPresets() {
  const [date, setDate] = useState<Date | null>(null);
  const today = new Date();
  const tomorrow = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1);
  const nextWeek = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 7);
  return (
    <PixelDatePicker
      label="Due date"
      value={date}
      onChange={setDate}
      clearable
      presets={[
        { label: 'Today', value: today },
        { label: 'Tomorrow', value: tomorrow },
        { label: 'Next week', value: nextWeek },
      ]}
    />
  );
}

With Min/Max

import { useState } from 'react';
import { PixelDatePicker } from '@pxlkit/ui-kit';

export function WithMinMax() {
  const [date, setDate] = useState<Date | null>(null);
  const today = new Date();
  const min = new Date(today.getFullYear(), today.getMonth(), today.getDate());
  const max = new Date(today.getFullYear(), today.getMonth() + 1, today.getDate());
  return (
    <PixelDatePicker
      label="Within one month"
      hint="Only the next 30 days are selectable"
      value={date}
      onChange={setDate}
      min={min}
      max={max}
    />
  );
}

Related