Skip to content

Date Picker

Form Inputs / Date and TimeSince 0.2.0
import { DatePicker } from '@absoluteui/components/date-picker';
import '@absoluteui/components/date-input/style.css';

Using Angular? See Date Picker for Angular.

A date field with a calendar popup (WAI-ARIA APG date picker dialog, non-modal variant). DatePicker composes a Date Input (date granularity) with a calendar button inside the field boundary; the button opens a Calendar in a role="dialog" popup in the top layer. Typing and picking produce the same PlainDate value. The picker, not its inner Date Input, is the form control that Form discovers.

  • Dates users may either type or look up by day of week (appointments, travel, deadlines).
  • Forms that need a date field with min/max bounds and unavailable days.
  • Well-known dates such as birth dates: use Date Input (a calendar makes distant years slow to reach).
  • An always-visible calendar: use Calendar.
  • Exact moments with a time of day: use DateTime Picker.
  • Date ranges: use Date Range Picker.
import { plainDate, plainDateToString } from '@absoluteui/core';
import { DatePicker, type DatePickerChangeDetail } from '@absoluteui/components/date-picker';
const host = document.createElement('div');
document.body.append(host);
DatePicker.create(host, { label: 'Check-in', name: 'checkIn', required: true, min: plainDate(2026, 1, 1), max: plainDate(2026, 12, 31) });
host.addEventListener('aui-change', (event) => {
const { value, validity } = (event as CustomEvent<DatePickerChangeDetail>).detail;
host.dataset.value = value && validity.valid ? plainDateToString(value) : '';
});
import { dayOfWeek } from '@absoluteui/core';
import { DatePicker } from '@absoluteui/components/date-picker';
const host = document.createElement('div');
document.body.append(host);
DatePicker.create(host, { label: 'Delivery date', firstDayOfWeek: 1, isDateDisabled: (date) => dayOfWeek(date) === 7 });
import { configureAbsoluteUI } from '@absoluteui/core';
configureAbsoluteUI({ common: { timezone: 'Europe/London' }, components: { datePicker: { closeOnSelect: false } } });
import { dayOfWeek } from '@absoluteui/core';
import { DatePicker } from '@absoluteui/components/date-picker';
import '@absoluteui/components/date-input/style.css';
import '@absoluteui/components/calendar/style.css';
import '@absoluteui/components/date-picker/style.css';
const host = document.querySelector<HTMLElement>('#delivery');
if (host) {
const picker = DatePicker.create(host, { label: 'Delivery date', name: 'delivery', isDateDisabled: (d) => dayOfWeek(d) === 7 });
host.addEventListener('aui-change', () => {
if (!picker.validity.valid) picker.reportValidity();
});
}
import { plainDate } from '@absoluteui/core';
import { DatePicker } from '@absoluteui/components/date-picker';
const host = document.createElement('div');
document.body.append(host);
DatePicker.create(host, { label: 'Check-in', name: 'checkIn', required: true, min: plainDate(2026, 1, 1) });
<div class="aui-date-picker" data-aui-control> (consumer host; registered with Form)
<div class="aui-date-picker__field aui-date-input"> (inner Date Input host; not registered)
<div class="aui-date-input__label">
<div class="aui-date-input__group" role="group">
<div class="aui-date-input__fields"> segments ... </div>
<div class="aui-date-picker__trailing"> (Date Input `trailing` option)
<button class="aui-date-picker__button" aria-haspopup="dialog" aria-expanded aria-controls aria-label="Choose date">
<div class="aui-date-picker__popup" role="dialog" aria-modal="false" aria-label="Choose date" popover="manual">
<div class="aui-calendar"> ... </div>
</div>
</div>
</div>
<div class="aui-date-input__description"> <div class="aui-date-input__hint"> <div class="aui-date-input__error">
<input type="hidden" name>
</div>
</div>

The popup sits inside the field group in the DOM (it renders in the top layer), so moving focus between the segments, the button, and the calendar does not mark the field touched, and a picker inside a modal Dialog stays inside the dialog’s subtree.

DatePicker.create(host: HTMLElement, options: DatePickerOptions): DatePicker

Global configuration key: datePicker (see Configuration).

Option Type Default Description
label string Field label and accessible name of the segment group. Required.
value PlainDate | null null Initial value; also the value restored by reset().
min PlainDate | null Inclusive lower bound (validation and calendar).
max PlainDate | null Inclusive upper bound (validation and calendar).
name string Form field name for the ISO 8601 hidden input.
description string Help text referenced by aria-describedby.
required boolean false Empty value is invalid (value-missing).
disabled boolean false Disables the segments and the calendar button.
readOnly boolean false Prevents editing; the calendar button is disabled.
isDateDisabled (date: PlainDate) => boolean Disable days in the calendar; a typed disabled day is invalid (‘This date is not available.’).
businessCalendar BusinessCalendarEngine | null Business calendar (since 0.9.0): non-working days and holidays are marked and named in the calendar.
disableNonWorking boolean "false" With businessCalendar: non-working days cannot be picked or typed (since 0.9.0).
firstDayOfWeek 1 | 2 | 3 | 4 | 5 | 6 | 7 First calendar column (ISO weekday). Default: the locale’s first day of week.
icons Partial<{ calendar: IconRef }> Icon slot overrides.
formControl boolean true Advanced, read at creation: false skips registration with Form, for composite controls such as Schedule Picker that register themselves (since 0.6.0). Set at creation only.
closeOnSelect boolean true Close the popup after a day is picked. Globally configurable.
locale string "en-US" Segment order, calendar, messages, formatting; forwarded to both children. Globally configurable. Inherited from common configuration.
timezone string (IANA) "UTC" Zone used only to determine today in the calendar. Globally configurable. Inherited from common configuration.
direction 'ltr' | 'rtl' "ltr" Arrow-key mirroring, calendar direction, and popup alignment. Globally configurable. Inherited from common configuration.
Property Type Description
host HTMLElement Consumer host; its children are owned by the component. Registered with Form.
input DateInput The inner Date Input (date granularity); not registered with Form.
calendar Calendar The Calendar inside the popup.
button HTMLButtonElement Calendar button inside the field boundary.
popup HTMLDivElement role=dialog aria-modal=false popup (popover=manual).
value PlainDate | null Current valid value, or null when empty or invalid.
validity DateInputValidity { valid, error, message, localTimeStatus }.
isOpen boolean Whether the popup is open.
name string | null Form field name.
label string Label text.
disabled boolean Disabled state.
dirty boolean Value differs from the initial value.
touched boolean Focus has left the field (segments, button, and popup) at least once.
options Readonly<DatePickerOptions> Current instance options.
Method Description
open(): void Open the calendar and focus the selected day (or today). No-op when disabled or read-only.
close(): void Close the calendar; focus inside the popup returns to the calendar button.
setValue(value: PlainDate | null): void Programmatic value (no aui-change).
update(options: Partial<DatePickerOptions>): void Merge and apply options to the field and calendar.
reportValidity(): boolean Show messages now and return validity.
setCustomError(message: string | null): void External error (server, cross-field rule); cleared when the user changes the value.
getFormValue(): string | null ISO 8601 YYYY-MM-DD, or null when empty or invalid.
focus(): void Focus the first date segment.
reset(): void Close, restore the initial value, and clear touched/submitted state (no aui-change).
destroy(): void Close and remove rendering, listeners, registration, and subscriptions of the picker and its children.
Event Detail Bubbles Cancelable Description
aui-change { value: PlainDate | null; validity: DateInputValidity } Yes No User typing or picking changed the value or validity. Dispatched from the picker host.
aui-navigate { month: { year: number; month: number } } Yes No Bubbles from the inner Calendar when the user changes the visible month.
aui-open null No No The popup opened.
aui-close null No No The popup closed.

All rules live in the aui.components cascade layer, so application styles override them without specificity battles.

Class Purpose
aui-date-picker Host; –disabled modifier.
aui-date-picker__field Host of the inner Date Input (aui-date-input classes apply inside).
aui-date-picker__trailing Button and popup container inside the Date Input group.
aui-date-picker__button Calendar button.
aui-date-picker__popup Top-layer dialog popup containing the Calendar (aui-calendar classes apply inside).
Slot Default icon
calendar calendar

Tokens this stylesheet reads (verified in CI). Override them in a theme or with the Theme Designer.

--aui-border-width | --aui-color-border | --aui-color-focus-ring | --aui-color-hover | --aui-color-surface | --aui-color-text | --aui-color-text-disabled | --aui-color-text-muted | --aui-control-height-md | --aui-duration-fast | --aui-easing-standard | --aui-focus-ring-offset | --aui-focus-ring-width | --aui-font-family-sans | --aui-icon-size-sm | --aui-radius-md | --aui-shadow-md | --aui-space-2 | --aui-space-3

interface DatePickerConfig { closeOnSelect: boolean }
interface DatePickerIcons { calendar: IconRef }
interface DatePickerChangeDetail { value: PlainDate | null; validity: DateInputValidity }
type Weekday = 1 | 2 | 3 | 4 | 5 | 6 | 7;

Pattern: WAI-ARIA APG Date Picker Dialog, non-modal variant: a labelled segmented date field (Date Input), a button with aria-haspopup=dialog, and a role=dialog popup containing the Calendar grid.

Key Context Action
Enter / Space Calendar button Open (or close) the calendar; focus moves to the selected day, else today.
Alt+ArrowDown Date segment focused Open the calendar (the segment value does not change).
Calendar keys Day focused Arrows, Home/End, PageUp/PageDown, Shift+PageUp/PageDown as in Calendar.
Enter / Space Day focused Pick the day, close the popup (closeOnSelect), and focus the calendar button.
Escape Popup open Close the popup and return focus to the calendar button. Inside a Dialog, only the popup closes.
Tab / Shift+Tab Popup open Moves through month buttons and the day; leaving the popup (other than to the calendar button) closes it.
Date Input keys Segment focused As in Date Input.
Element Role
segment group group (Date Input)
calendar button button (native), aria-haspopup=dialog
popup dialog (aria-modal=false)
calendar grid (Calendar)
icons none (aria-hidden=true)
Attribute Element Value
aria-label calendar button “Choose date”, or “Change date, <long date>” when a date is set
aria-expanded calendar button “true” while the popup is open
aria-controls calendar button Popup id
aria-label popup Localized “Choose date”
aria-modal popup “false” (non-modal; the page stays reachable)

Opening moves focus into the grid; closing with focus inside the popup returns it to the calendar button. Outside pointer presses close the popup. Moving focus between the segments, button, and popup does not mark the field touched.

Criterion Level Note
1.3.1 Info and Relationships A
1.4.3 Contrast (Minimum) AA
1.4.11 Non-text Contrast AA
1.4.13 Content on Hover or Focus AA
2.1.1 Keyboard A
2.1.2 No Keyboard Trap A
2.4.3 Focus Order A
2.4.7 Focus Visible AA
3.3.1 Error Identification A
3.3.2 Labels or Instructions A
3.3.3 Error Suggestion AA
4.1.2 Name, Role, Value A
4.1.3 Status Messages AA

Automated test results: Accessibility and compliance.

  • The segments remain the primary input; the calendar is an alternative (APG).
  • The popup is non-modal: focus is not trapped, and the rest of the page stays reachable.
  • A typed day rejected by isDateDisabled produces a localized validation message, so the typed and picked paths agree.
  • Screen-reader behavior of the popup (dialog announcement, month changes) has not yet been manually verified with NVDA, JAWS, and VoiceOver.

empty, partial (incomplete), valid, invalid (Date Input errors, unavailable day, custom), open, disabled, read-only.

From Trigger To
closed button, or Alt+ArrowDown on a segment open; focus on the selected day or today; aui-open
open pick a day value written; aui-change if changed; closed and focus on the button (closeOnSelect)
open Escape closed; focus on the button; aui-close
open pointer outside, or Tab out of the popup closed
any user types aui-change; the calendar follows the typed date while open
any reset() closed, initial value, pristine, untouched
  • The segments stay the primary input; the calendar is an alternative.
  • The popup is non-modal: focus is not trapped and the page stays reachable. Escape and outside presses close it through the shared overlay stack, so only the topmost overlay responds.
  • A typed day rejected by isDateDisabled gets a localized message, so the typed and picked paths agree.
  • Manual screen-reader verification is pending.

The field fits its content like Date Input. The popup is positioned under the field (bottom-start), flips above when there is no room, and scrolls when taller than the available height (--aui-available-height).

Segment order follows the locale; the button sits at the inline end of the field; the popup aligns to the field’s inline start; the calendar mirrors arrows and chevrons.

Role tokens only; verified in the light, dark, and high-contrast themes. In forced-colors mode the popup has a CanvasText border.

configureAbsoluteUI({ common: { timezone: 'Europe/London' }, components: { datePicker: { closeOnSelect: false } } });

Changes apply to existing instances immediately. Configuration changes that do not change locale, timezone, or direction do not rebuild the field, so partially typed input is kept.

Instance options override global and theme defaults. All options are dynamic.

Values are PlainDate and never shift. The configured timezone (default UTC, never the host zone) only determines “today” in the calendar, which is focused when the field is empty.

icons.calendar (default calendar).

All text uses textContent and Intl formatting; no HTML parsing. The popup uses the Popover API; no inline styles or handlers. CSP-safe.

No persistence, network access, or telemetry. When name is set, the inner Date Input’s hidden input carries YYYY-MM-DD.

One Date Input and one Calendar per picker, created once; opening re-renders only the calendar month. Budget: under 10 ms average per DatePicker.create across 100 instances.

100 pickers per page.

  • Opening an empty field focuses today in the configured zone: at 2026-03-15 10:30 UTC with timezone: 'Pacific/Kiritimati', 16 March.
  • Re-picking the current value closes the popup without aui-change.
  • Picking a date while the segments hold an incomplete entry replaces it and emits aui-change.
  • Inside a modal Dialog, the first Escape closes the popup and the second closes the dialog.
  • Opening from an empty required field does not show “This field is required.” until focus leaves the whole field.
Code Category Cause
DATE_PICKER_OPTIONS configuration Missing label, or value/min/max not a PlainDate.
CALENDAR_OPTIONS configuration min > max, firstDayOfWeek outside 1-7, or a non-function isDateDisabled.
CFG_* configuration Invalid locale, timezone, direction, or closeOnSelect.
Validation (validity.error) validation Date Input errors (incomplete, invalid-date, value-missing, range-underflow, range-overflow) and custom (consumer error, or “This date is not available.”).

A failed construction leaves no rendering, listeners, registrations, or subscriptions behind.

  • Gregorian calendar only; years 1 to 9999; single dates only (for ranges use Date Range Picker).
  • Validation messages follow Date Input timing; validationTiming is not supported.
  • A typed unavailable day is reported as custom; a consumer custom error takes precedence over it.
  • No Cancel/OK buttons: picking commits.
  • Screen-reader behavior has not yet been manually verified with NVDA, JAWS, and VoiceOver.
  • Minute granularity.
  • HTML in the label, help text, or messages.