One trigger opens a dialog with two side-by-side Calendars (the given month plus the next). The first pick sets the start and announces"choose end date" through a polite live region; the second pick sets the end. Days between get an in-range treatment, and every pick dispatches an ink:range:change event.
The locale is set so day names render in English (formatLong follows the runtime default locale otherwise). Pick the 10th, then the 15th: the stay hidden input holds"2026-09-10", stay-end holds"2026-09-15", and the days between read", in selected range" in their labels. The dialog stays open after every pick so the range can be adjusted; "Clear"resets both hidden inputs and the paint and dispatchesink:range:change with { start: "", end: "" }.
A value with ISO start/end paints the range server-side — the start (resp. end) day renders pre-selected in the first (resp. second) calendar, with in-range middles labelled. Malformed dates select nothing. Picking a second day earlier than the start restarts the range at the new start instead (the end stays empty); a first calendar month falls back to the start month, then today, and the second always shows the following month.
Props
Prop
Type
Default
Description
id
string
-
Required. Names Field/panel/message ids (trigger itself carries no id).
label
string
-
Visible label; kept in the trigger name and dialog name.
name
string
id
Start hidden-input form name; carries the ISO start date.
endName
string
name + "-end"
End hidden-input form name; carries the ISO end date.
value
{ start?: string; end?: string }
-
Preselected range; each part is a strict ISO date, malformed parts select nothing.
min
string (ISO)
-
Earliest selectable date in both calendars; earlier days render disabled.
max
string (ISO)
-
Latest selectable date in both calendars; later days render disabled.
month
string ("YYYY-MM")
-
First calendar's month; the second always shows the following month. Falls back to the start month, then today.
locale
string
-
BCP 47 locale for day and month names; runtime default when omitted.
hint
string
-
Help text below the trigger.
error
string
-
Error message; sets aria-invalid on the trigger.
disabled
boolean
false
Non-interactive; trigger cannot open.
class
string
-
Style passthrough.
Accessibility
Trigger keeps the label in its accessible name, with aria-haspopup="dialog", aria-expanded, aria-controls. It is a native button, so it stays keyboard-focusable.
Panel is a dialog named "{label} options" holding two Calendars labelled "{label}: start date" and "{label}: end date" (same table-of-buttons keyboard map as Calendar) plus a "Clear" button.
Opening moves focus to the current day; Escape closes and refocuses the trigger; clicking outside closes. The panel stays open after picks, with focus left on the picked day.
A polite live region announces "Start date … selected. Choose end date." after the first pick (and on Clear).
Range endpoints keep the ", selected" suffix; days strictly between gain ", in selected range".
Every pick and Clear dispatches ink:range:change with { start, end } (ISO strings, empty when unset).