Skip to content

DateTime Picker

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

Using Angular? See DateTime Picker for Angular.

A field for an exact moment (date and time of day) with a picker popup. DateTimePicker composes a Date Input (minute granularity) with a trigger button inside the field boundary. The button opens a non-modal role="dialog" popup in the top layer (WAI-ARIA APG date picker dialog) with a Calendar, a listbox of suggested times, and a Done button. The value is an Instant. Typed and picked wall-clock times are resolved the same way: in the configured timezone, using the disambiguation policy for daylight-saving transitions (section 8.5). The picker, not its inner Date Input, is the form control that Form discovers.

  • Exact moments users may type or look up: appointments, departures, deadlines with a time.
  • Scheduling forms that need min/max instants, unavailable days, and explicit timezone handling.
  • A date without a time: use Date Picker.
  • A time of day without a date or timezone (opening hours): use Time Picker.
  • Moments whose timezone the user chooses per value: pair this picker with a timezone select and update timezone.
  • Date-time ranges: not supported yet (use a Date Range Picker with Time Pickers).

Required departure in New York with bounds

Section titled “Required departure in New York with bounds”
import { instantToString, parseInstant } from '@absoluteui/core';
import { DateTimePicker, type DateTimePickerChangeDetail } from '@absoluteui/components/date-time-picker';
const host = document.createElement('div');
document.body.append(host);
DateTimePicker.create(host, {
label: 'Departure',
name: 'departure',
required: true,
timezone: 'America/New_York',
min: parseInstant('2026-01-01T05:00:00Z'),
max: parseInstant('2027-01-01T05:00:00Z'),
});
host.addEventListener('aui-change', (event) => {
const { value, validity } = (event as CustomEvent<DateTimePickerChangeDetail>).detail;
host.dataset.value = value && validity.valid ? instantToString(value) : '';
});

Reject nonexistent and repeated local times

Section titled “Reject nonexistent and repeated local times”
import { DateTimePicker } from '@absoluteui/components/date-time-picker';
const host = document.createElement('div');
document.body.append(host);
const picker = DateTimePicker.create(host, { label: 'Maintenance window', timezone: 'Europe/Berlin', disambiguation: 'reject', step: 15, hourCycle: 'h23' });
host.addEventListener('aui-change', () => {
if (picker.validity.error === 'nonexistent-time' || picker.validity.error === 'ambiguous-time') picker.reportValidity();
});

Hourly suggestions that keep the popup open

Section titled “Hourly suggestions that keep the popup open”
import { configureAbsoluteUI } from '@absoluteui/core';
configureAbsoluteUI({ common: { timezone: 'Asia/Kolkata' }, components: { dateTimePicker: { step: 60, closeOnSelect: false } } });
import { instantToString } from '@absoluteui/core';
import { DateTimePicker } from '@absoluteui/components/date-time-picker';
import '@absoluteui/components/date-input/style.css';
import '@absoluteui/components/calendar/style.css';
import '@absoluteui/components/date-time-picker/style.css';
const host = document.querySelector<HTMLElement>('#pickup');
if (host) {
const picker = DateTimePicker.create(host, { label: 'Pickup', name: 'pickup', timezone: 'Europe/Berlin', step: 15 });
host.addEventListener('aui-change', () => {
host.dataset.utc = picker.value ? instantToString(picker.value) : '';
});
}
import { parseInstant } from '@absoluteui/core';
import { DateTimePicker } from '@absoluteui/components/date-time-picker';
const host = document.createElement('div');
document.body.append(host);
DateTimePicker.create(host, { label: 'Departure', name: 'departure', required: true, timezone: 'America/New_York', min: parseInstant('2026-01-01T05:00:00Z') });
<div class="aui-date-time-picker" data-aui-control> (consumer host; registered with Form)
<div class="aui-date-time-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"> date and time segments ... </div>
<div class="aui-date-time-picker__trailing"> (Date Input `trailing` option)
<button class="aui-date-time-picker__button" aria-haspopup="dialog" aria-expanded aria-controls aria-label="Choose date and time">
<div class="aui-date-time-picker__popup" role="dialog" aria-modal="false" aria-label="Choose date and time" popover="manual">
<div class="aui-date-time-picker__body">
<div class="aui-date-time-picker__calendar aui-calendar"> ... </div>
<div class="aui-date-time-picker__time">
<div class="aui-date-time-picker__time-label">Time</div>
<div class="aui-date-time-picker__listbox" role="listbox" tabindex="0" aria-labelledby aria-activedescendant>
<div class="aui-date-time-picker__option" role="option" aria-selected [aria-disabled]>9:00 AM</div> ...
<div class="aui-date-time-picker__footer"><button class="aui-date-time-picker__done">Done</button></div>
<div class="aui-date-input__description"> <div class="aui-date-input__hint"> <div class="aui-date-input__error">
<input type="hidden" name>

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

DateTimePicker.create(host: HTMLElement, options: DateTimePickerOptions): DateTimePicker

Global configuration key: dateTimePicker (see Configuration).

Option Type Default Description
label string Field label and accessible name of the segment group. Required.
value Instant | null null Initial value; also the value restored by reset().
min Instant | null Inclusive lower bound (validation, calendar days, and suggested times).
max Instant | null Inclusive upper bound (validation, calendar days, and suggested times).
name string Form field name for the hidden input carrying the ISO 8601 instant (Z).
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 trigger button.
readOnly boolean false Prevents editing; the trigger 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 time is picked (picking a day keeps it open). Globally configurable.
step number (integer 1..720, minutes) 30 Minutes between suggested times, from midnight. Globally configurable.
hourCycle 'auto' | 'h12' | 'h23' "auto" Clock of the segments and the suggested times; ‘auto’ follows the locale. Globally configurable.
locale string "en-US" Segment order, calendar, time labels, messages, formatting; forwarded to both children. Globally configurable. Inherited from common configuration.
timezone string (IANA) "UTC" Zone in which wall-clock input is resolved and values are displayed; also today in the calendar. Globally configurable. Inherited from common configuration.
disambiguation 'compatible' | 'earlier' | 'later' | 'reject' "compatible" DST policy for nonexistent and repeated wall-clock times (typed or picked). 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 (minute granularity); not registered with Form.
calendar Calendar The Calendar inside the popup.
button HTMLButtonElement Trigger button inside the field boundary.
popup HTMLDivElement role=dialog aria-modal=false popup (popover=manual).
listbox HTMLDivElement role=listbox of suggested times (aria-activedescendant).
doneButton HTMLButtonElement Done button that closes the popup.
value Instant | 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<DateTimePickerOptions> Current instance options.
Method Description
open(): void Open the popup and focus the selected day (or today). No-op when disabled or read-only.
close(): void Close the popup; focus inside the popup returns to the trigger button.
setValue(value: Instant | null): void Programmatic value (no aui-change).
update(options: Partial<DateTimePickerOptions>): void Merge and apply options to the field, calendar, and listbox.
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 instant with Z, or null when empty or invalid.
focus(): void Focus the first 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: Instant | null; validity: DateInputValidity } Yes No User typing or picking changed the value, validity, or DST status. 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-time-picker Host; –disabled modifier.
aui-date-time-picker__field Host of the inner Date Input (aui-date-input classes apply inside).
aui-date-time-picker__trailing Button and popup container inside the Date Input group.
aui-date-time-picker__button Trigger button.
aui-date-time-picker__popup Top-layer dialog popup.
aui-date-time-picker__body Calendar and time column (wraps on narrow viewports).
aui-date-time-picker__calendar Calendar host (aui-calendar classes apply inside).
aui-date-time-picker__time Time column.
aui-date-time-picker__time-label Visible ‘Time’ label naming the listbox.
aui-date-time-picker__listbox Scrollable listbox of suggested times.
aui-date-time-picker__option Suggested time; –active modifier for the active descendant; aria-selected and aria-disabled states.
aui-date-time-picker__footer Footer holding the Done button.
aui-date-time-picker__done Done button.
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-selected | --aui-color-selected-text | --aui-color-surface | --aui-color-text | --aui-color-text-disabled | --aui-color-text-muted | --aui-control-height-md | --aui-control-height-sm | --aui-duration-fast | --aui-easing-standard | --aui-focus-ring-offset | --aui-focus-ring-width | --aui-font-family-sans | --aui-font-size-md | --aui-font-size-sm | --aui-font-weight-medium | --aui-icon-size-sm | --aui-radius-md | --aui-radius-sm | --aui-shadow-md | --aui-space-1 | --aui-space-2 | --aui-space-3

interface DateTimePickerConfig { closeOnSelect: boolean; step: number; hourCycle: HourCycle }
type HourCycle = 'auto' | 'h12' | 'h23';
interface DateTimePickerIcons { calendar: IconRef }
interface DateTimePickerChangeDetail { value: Instant | null; validity: DateInputValidity }
type Weekday = 1 | 2 | 3 | 4 | 5 | 6 | 7;

Pattern: WAI-ARIA APG Date Picker Dialog, non-modal variant, extended with an APG listbox of times: a labelled segmented date-time field (Date Input), a button with aria-haspopup=dialog, and a role=dialog popup containing the Calendar grid, a role=listbox of suggested times (aria-activedescendant), and a Done button.

Key Context Action
Enter / Space Trigger button Open (or close) the popup; focus moves to the selected day, else today.
Alt+ArrowDown Segment focused Open the popup (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, keeping the time of day (09:00 or the first available time when empty). The popup stays open.
ArrowUp / ArrowDown / Home / End / PageUp / PageDown Time listbox Move the active time.
Printable characters Time listbox Typeahead to the first matching time.
Enter / Space Time listbox Pick the active time, keeping the date; closes and focuses the trigger (closeOnSelect).
Tab / Shift+Tab Popup open Month buttons, the day, the time listbox, then Done; leaving the popup (other than to the trigger) closes it.
Enter / Space Done Close the popup and focus the trigger.
Escape Popup open Close the popup and return focus to the trigger. Inside a Dialog, only the popup closes.
Date Input keys Segment focused As in Date Input.
Element Role
segment group group (Date Input)
trigger button button (native), aria-haspopup=dialog
popup dialog (aria-modal=false)
calendar grid (Calendar)
time list listbox with option children
Done button (native)
icons none (aria-hidden=true)
Attribute Element Value
aria-label trigger button “Choose date and time”, or “Change date and time, <long date and short time>” when a value is set
aria-expanded trigger button “true” while the popup is open
aria-controls trigger button Popup id
aria-label popup Localized “Choose date and time”
aria-modal popup “false” (non-modal; the page stays reachable)
aria-labelledby listbox Visible “Time” label
aria-activedescendant listbox Active option id while open
aria-selected option “true” for the field’s time of day
aria-disabled option “true” when the time resolves outside min/max on the current date
aria-describedby segment group Help text, time-zone and DST hint, and error (Date Input)

Opening moves focus into the calendar grid; closing with focus inside the popup returns it to the trigger button before the popup hides. 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.1 Use of Color 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 popup is an alternative (APG).
  • The popup is non-modal: focus is not trapped, and the rest of the page stays reachable.
  • DST adjustments are explained in text (hint referenced by aria-describedby), not only by the changed value.
  • The time zone is always stated in the hint, so the meaning of the wall-clock time is explicit.
  • The active time has an outline, not only a color change; unavailable times are also struck through.
  • Screen-reader behavior of the popup (dialog announcement, listbox inside a dialog) has not yet been manually verified with NVDA, JAWS, and VoiceOver.
  • Hint changes after picking are conveyed through aria-describedby and are not announced live.

empty, partial (incomplete), valid, valid with a DST hint (adjusted or repeated time), invalid (Date Input errors including nonexistent-time/ambiguous-time under 'reject', 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; the active time is the field’s time (else 09:00); aui-open
open pick a day the date changes and the time of day is kept (09:00, or the first available time, when empty); aui-change if changed; stays open
open pick a time the time changes and the date is kept (the focused day when empty); aui-change if changed; closed and focus on the button (closeOnSelect)
open Done, or 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 and listbox follow while open
any reset() closed, initial value, pristine, untouched
  • The segments stay the primary input; the popup 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.
  • The time zone is always stated in the hint (for example “Time zone: Eastern Daylight Time (GMT-04:00)”). DST adjustments are explained in text, the same way for typed and picked input.
  • The active time has an outline, and unavailable times are struck through, so neither state relies on color alone.
  • 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). On narrow viewports the time column wraps below the calendar and keeps a minimum height of four options.

Segment order follows the locale. The trigger sits at the inline end of the field. The popup aligns to the field’s inline start, the calendar mirrors arrows and chevrons, and the time column follows the calendar in inline order.

Role tokens only; verified in the light, dark, and high-contrast themes. In forced-colors mode the popup has a CanvasText border, the selected time uses Highlight/HighlightText, and unavailable times use GrayText.

configureAbsoluteUI({ common: { timezone: 'America/New_York' }, components: { dateTimePicker: { step: 15, hourCycle: 'h23' } } });

Changes apply to existing instances immediately. Changes that affect only step or closeOnSelect do not rebuild the field, so partially typed input is kept.

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

Values are Instant. Wall-clock input is resolved in the configured timezone (default UTC, never the host zone) with disambiguation. Picked input goes through the same Date Input code as typed input.

Case (America/New_York) compatible (default) earlier later reject
2026-03-08 02:30 (does not exist) 03:30 EDT (07:30Z), hint “Adjusted to 3:30 AM…” 01:30 EST (06:30Z) 03:30 EDT error nonexistent-time
2026-11-01 01:30 (occurs twice) 01:30 EDT (05:30Z), hint “…the earlier occurrence (GMT-4) is used.” 01:30 EDT 01:30 EST (06:30Z) error ambiguous-time

The segments keep the wall-clock time the user chose, so changing disambiguation or timezone re-resolves it. min and max are instants; the calendar disables days whose local date (in timezone) is outside them, and the listbox disables times that resolve outside them. “Today” in the calendar is today in timezone.

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 the ISO 8601 instant with Z.

One Date Input, one Calendar, and one listbox per picker, created once. The options (1440 / step, 48 by default) are rebuilt only when locale, the hour cycle, or step changes. Picking updates their states in place. Budget: under 15 ms average per DateTimePicker.create across 100 instances.

100 pickers per page; step: 1 (1440 options) is supported but slower to scroll.

  • Picking a day in an empty field uses 09:00 (or the first available time at or after it). Picking a time in an empty field uses the calendar’s focused day (today in timezone unless the user navigated).
  • Picking 2026-03-08 while the field shows 02:30 in New York keeps “02:30” in the segments, stores 03:30 EDT, and shows the adjustment hint. With 'reject' the value is null and the error is shown immediately.
  • A time with a DST error under 'reject' stays listed and pickable, so the user sees why it is invalid. Picking another time keeps the date.
  • Re-picking the current day or time emits no 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_TIME_PICKER_OPTIONS configuration Missing label; value/min/max not an Instant; min > max; non-function isDateDisabled.
CALENDAR_OPTIONS configuration firstDayOfWeek outside 1-7.
CFG_* configuration Invalid locale, timezone, disambiguation, direction, step, hourCycle, or closeOnSelect.
Validation (validity.error) validation Date Input errors (incomplete, invalid-date, value-missing, range-underflow, range-overflow, nonexistent-time, ambiguous-time) 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; minute precision.
  • Suggested times start at midnight every step minutes; they are not offset to min.
  • 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 button: picking commits, and Done only closes.
  • DST hints are referenced by aria-describedby and are not announced live.
  • Screen-reader behavior has not yet been manually verified with NVDA, JAWS, and VoiceOver.
  • Seconds or milliseconds.
  • Choosing a timezone inside the picker.
  • HTML in the label, help text, or messages.