Components · Date & time

Date-time range

A start/end pair of DateTimePickers under one labeled, Surface-grouped control with built-in start ≤ end validation — the end half's lower bound is pinned to the chosen start, and an inline role=alert catches any inverted range. Below, the SchedulerStrip lays a 30-min slot lattice on a single day.

DateTimeRangePicker · datetime range · start ≤ end · timezoneLabel "PT"
07/08/2026
08:00 AM
07/11/2026
05:00 PM
PT
July 2026
28
29
30
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
1
2
3
4
5
6
7
8

Band = the selected span · endpoints filled · Jul 2 = today ring.

Wed Jul 8, 8:00 AM Sat Jul 11, 5:00 PM 3 days · 9 hrs

Beagle intake & vetting — Kern County. The end half's lower bound is pinned to the start (07/08), so the native picker won't open earlier. Validity is reported via onValidityChange; isInvertedRange(start,end) is the shared predicate. Values → 2026-07-08T08:00 and 2026-07-11T17:00.

inverted range · end < start → error ring + inline alert
07/11/2026
07/08/2026

startLabel / endLabel default to Start / End; both halves share granularity, stepMinutes, and timezoneLabel. Pass error to override the default inverted-range message.

SchedulerStrip · quick slots — 30-min lattice, all-day strip, now-line on today
Thursday, July 2Today
4 events
All day
Pick a start · 30-min slots Now 10:20 AM
Placed block · lane-packed on the grid

onSelectSlot(slotStart) fires on an empty slot (granularity = slotMinutes, default 30); onSelectEvent(event) on a block. Slot hover is an accent 7% wash, the selected slot rings accent, and 11:00 holds the roving :focus. Today + the now-line read via text/aria, not color alone. Below sm the grid collapses to a sorted agenda list.

startValue / endValue
Controlled; the parent owns both. Each half is a DateTimePicker, sharing granularity / stepMinutes / timezoneLabel / min / max. React via onStartChange / onEndChange.
validation
Inverted ranges (end before start) flip the end half to its error ring and show an inline role=alert. error overrides the message; onValidityChange(valid) fires when validity flips. isInvertedRange is exported.
SchedulerStrip · events
events: CalendarEvent[] + date. Timed events lane-pack as positioned blocks; untimed go to the all-day strip. today drives the now-line + Today chip.
SchedulerStrip · slots
onSelectSlot(slotStart) on an empty slot, onSelectEvent(event) on a block. slotMinutes (default 30) sets the selectable lattice; the slot grid is a roving-tabindex group (ArrowUp/Down + Enter).

Source · ds/datetime/DateTimeRangePicker.tsx (two DateTimePickers + isInvertedRange + inline role=alert) · ds/datetime/SchedulerStrip.tsx (ds/calendar dayHours / splitDayEvents / placeTimed on a Surface).