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';
Prop
Type
Default
Description
value
Date | null
—
The picked day, null for none; leave unset for an uncontrolled picker.
defaultValue
Date
—
Initial day while uncontrolled.
onChange
(date: Date | null) => void
—
Called with the day picked, at its start, or null once cleared.
min
Date
—
First day that can be picked.
max
Date
—
Last day that can be picked.
disabledDates
Date[] | ((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.
placeholder
string
'Select date'
Text shown while no day is picked.
clearable
boolean
false
Shows 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.
label
string
—
Label rendered above the trigger.
hint
string
—
Helper text below the field; hidden while error is set.
error
string
—
Error message below the field; marks the trigger invalid.
name
string
—
Form field name — a hidden input submits the day as YYYY-MM-DD.
id
string
—
id of the trigger; generated when left out.
data-testid
string
—
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
Key
Action
When
ArrowLeft
Move focus to the previous day
—
ArrowRight
Move focus to the next day
—
ArrowUp
Move focus to the previous week
—
ArrowDown
Move focus to the next week
—
Home
Move focus to the first day of the week
—
End
Move focus to the last day of the week
—
PageUp
Move focus to the same day of the previous month (its last day when shorter)
—
PageDown
Move focus to the same day of the next month (its last day when shorter)
—
Shift+PageUp
Move focus to the same day of the previous year
—
Shift+PageDown
Move focus to the same day of the next year
—
Enter
Select the focused day and close the popover
—
Space
Select 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"
/>
);
}
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}
/>
);
}