Date Picker
A typable date and time field with a calendar and clock popup, parsed against the locale.
Live demo
Date Picker edits a date, a clock time, or both. A text field holds the value and parses what the user types — the locale's own numeric order (3/8/2026 in en-US, 08/03/2026 in en-GB), ISO YYYY-MM-DD whatever the locale, the compact runs (38, 20260308, 930, 9:30 pm), and a bare day number against the current month.
Typing is the fast path and the accessible one; the popup is a convenience on top of it, not a substitute. The arrow keys step the value (a day, or a month with PageUp/PageDown; one step on the clock), and Alt+↓ opens the popup. The popup hosts Calendar for the grid and a step-quantized listbox for the clock.
The value is a wall-clock reading, not an instant. mode="date" produces YYYY-MM-DD, mode="time" produces HH:mm[:ss], and mode="datetime" produces YYYY-MM-DDTHH:mm[:ss] — no Z, no offset, exactly the native date / time / datetime-local contract. Nothing is ever converted to an instant, so no value can be shifted across a DST boundary: stepping onto a spring-forward day gives that day, and 01:30 + one hour gives 02:30 even where that hour does not exist locally. A string carrying an offset is rejected rather than silently reinterpreted; a caller that needs an instant applies a timezone at the edge, where the zone is actually known.
min/max clamp, disabledWeekdays rules out weekdays, and an isDateDisabled predicate rules out individual days — all three are honoured by the grid and by the typed field, so a date that cannot be clicked cannot be typed in behind the grid's back. The predicate is the same name and the same shape Calendar takes, so one function serves the field and the grid it hosts. Unparseable or unavailable input is flagged in place (and announced) rather than silently reverted, so the user can fix what they wrote. It is form-associated: give it a name and the canonical value submits. step is in seconds, the native contract, defaulting to 60; a sub-minute step shows the seconds field. The 12/24-hour posture follows the locale unless hour-cycle forces it.
When to use
How this component composes with the rest of the set.
Props
26 props, straight from the manifest.
| Prop | Type | Default | Bindings | Description |
|---|---|---|---|---|
Appearance
Sizes
sm
Compact.
md
Default.
lg
Large.
States
focus-within
An input is focused. The control border takes the accent and a token ring appears.
invalid
Unparseable, unavailable, or consumer-flagged input. The border takes the danger token and the text is left in place to be fixed.
readonly
Shown but not editable: a muted field with no popup and no clear.
disabled
The whole field disabled: muted control, no typing, popup, or clearing.
time-selected
The chosen time in the popup's listbox.
Anatomy
The named parts that make up the component, with their selectors.
datepicker
The wrapper carrying the size, mode, and state classes and stacking the label over the control.
label
The optional visible label; it names the control group, and each input inside it is named in turn.
control
The bordered role="group" row holding the input(s), the clear button, and the popup trigger.
input
The typable field. datetime mode renders two — a date and a time — each separately named.
clear
Empties the field and returns focus to it. Shown whenever the field holds a value and is editable.
trigger
Opens the popup. Carries aria-haspopup="dialog", aria-expanded, and aria-controls.
panel
The popup body inside Popover: the calendar grid, the time listbox, or both side by side.
times
The role="listbox" of step-quantized times, scrolled to the selection when the popup opens.
time-option
One role="option" time. The list keeps a single tab stop; the arrows walk it.
Tokens & coverage
What the component consumes, checked live against what the algorithm produces.
Live coverage check against the xtyle-default register
(derive(xtyleDefault, { anchors }) →
coverComponent(manifest, register)). Every token this component
consumes must be a key the algorithm produces.
--accent
--accent-fg
--bg-0
--bg-1
--border-thick
--border-thin
--danger
--duration-fast
--ease-standard
--fg-0
--fg-1
--fg-2
--fg-disabled
--font-sans
--line-2
--neutral-bg
--radius-md
--radius-sm
--ring
--space-1
--space-2
--space-3
--space-4
--space-5
--space-6
--space-7
--state-disabled
--state-hover
--state-press
--text-body
--text-sm
Accessibility
Code
Date, datetime, and time
The three modes. Every one of them is typable — the popup is a convenience, not the only way in.
<xtyle-date-picker label="Start date" value="2026-03-08"></xtyle-date-picker>
<xtyle-date-picker label="Appointment" mode="datetime" value="2026-03-08T09:30" step="900"></xtyle-date-picker>
<xtyle-date-picker label="Opens at" mode="time" value="09:00" min="08:00" max="18:00" step="1800"></xtyle-date-picker>
<script lang="ts">
import { DatePicker } from "@xtyle/svelte";
let start = $state("2026-03-08");
let appointment = $state("2026-03-08T09:30");
</script>
<DatePicker label="Start date" bind:value={start} />
<DatePicker label="Appointment" mode="datetime" bind:value={appointment} step={900} />
<DatePicker label="Opens at" mode="time" value="09:00" min="08:00" max="18:00" step={1800} />
---
import { DatePicker } from "@xtyle/astro";
---
<DatePicker label="Start date" value="2026-03-08" />
<DatePicker label="Appointment" mode="datetime" value="2026-03-08T09:30" step={900} />
<DatePicker label="Opens at" mode="time" value="09:00" min="08:00" max="18:00" step={1800} />
Bounds, weekends, and holidays
A range, a weekday rule-out, and a per-date predicate — all three enforced on the grid and on typed input, plus form association.
<xtyle-date-picker
label="Delivery date"
min="2026-03-08"
max="2026-04-30"
disabled-weekdays="0,6"
name="delivery"
required
></xtyle-date-picker>
<script type="module">
// the same predicate Calendar takes: it rules out individual days on the grid and on typed input alike
const holidays = new Set(["2026-03-17", "2026-04-03"]);
document.querySelector("xtyle-date-picker").isDateDisabled = (iso) => holidays.has(iso);
</script>
<script lang="ts">
import { DatePicker } from "@xtyle/svelte";
const holidays = new Set(["2026-03-17", "2026-04-03"]);
let delivery = $state("");
</script>
<DatePicker
label="Delivery date"
bind:value={delivery}
min="2026-03-08"
max="2026-04-30"
disabledWeekdays={[0, 6]}
isDateDisabled={(iso) => holidays.has(iso)}
name="delivery"
required
/>
---
import { DatePicker } from "@xtyle/astro";
---
<DatePicker
label="Delivery date"
min="2026-03-08"
max="2026-04-30"
disabledWeekdays={[0, 6]}
name="delivery"
required
/>
Locale, zone, and the 12/24-hour clock
The same value read and typed three ways. The clock posture follows the locale unless hour-cycle forces it, and timezone pins what "today" means without shifting the value.
<xtyle-date-picker label="Date (US)" locale="en-US" value="2026-03-08"></xtyle-date-picker>
<xtyle-date-picker label="Date (GB)" locale="en-GB" value="2026-03-08"></xtyle-date-picker>
<xtyle-date-picker label="Forced 24-hour" mode="time" locale="en-US" hour-cycle="24" value="21:30"></xtyle-date-picker>
<!-- "today" is the venue's day, not the visitor's — the grid rings it and a typed "22" resolves against it -->
<xtyle-date-picker label="Doors open" timezone="Asia/Tokyo" first-day-of-week="1"></xtyle-date-picker>
<DatePicker label="Date (US)" locale="en-US" value="2026-03-08" />
<DatePicker label="Date (GB)" locale="en-GB" value="2026-03-08" />
<DatePicker label="Forced 24-hour" mode="time" locale="en-US" hourCycle="24" value="21:30" />
<DatePicker label="Doors open" timezone="Asia/Tokyo" firstDayOfWeek={1} />
<DatePicker label="Date (US)" locale="en-US" value="2026-03-08" />
<DatePicker label="Date (GB)" locale="en-GB" value="2026-03-08" />
<DatePicker label="Forced 24-hour" mode="time" locale="en-US" hourCycle="24" value="21:30" />
<DatePicker label="Doors open" timezone="Asia/Tokyo" firstDayOfWeek={1} />