DateRangePicker
A calendar popover for picking a start and end date, for travel dates, a booking window, or a reporting period. It builds on DatePicker: same sizes, variants, constraints, and locale handling.
Playground
<DateRangePicker
label="Travel dates"
/>01 Examples
Booking a stay
closeOnSelect (the default) dismisses the popover once the range is complete, so the flow ends without a second gesture.<script>
import { DateRangePicker, type DateRange } from '@urbicon-ui/blocks';
let stay = $state<DateRange | undefined>(undefined);
</script>
<DateRangePicker
label="Check-in / check-out"
bind:value={stay}
placeholder="Pick your dates"
clearable
/>Bounded to a bookable window
minDate and maxDate fence the calendar; isDateDisabled blocks the irregular gaps a fixed range cannot express, here every Sunday.<script>
import { DateRangePicker, type DateRange } from '@urbicon-ui/blocks';
let bookable = $state<DateRange | undefined>(undefined);
const TODAY = new Date(2026, 7, 3);
const IN_90_DAYS = new Date(2026, 10, 1);
</script>
<DateRangePicker
label="Bookable period"
bind:value={bookable}
minDate={TODAY}
maxDate={IN_90_DAYS}
isDateDisabled={(d) => d.getDay() === 0}
helper="Next 90 days, Sundays excluded"
/>Reporting period in a form
name writes two hidden inputs, period_start and period_end, each carrying the serialised date, so the submitted form has the ISO values, not the locale-formatted display text. An empty range submits both as an empty string.<script>
import { DateRangePicker, type DateRange } from '@urbicon-ui/blocks';
let reportRange = $state<DateRange | undefined>(undefined);
</script>
<DateRangePicker
label="Reporting period"
name="period"
bind:value={reportRange}
required
helper="Submitted as period_start and period_end"
/>02 Customization
The trigger and the calendar are styled separately: inputVariant takes the Input ladder (outlined, filled, ghost, underline) and calendarVariant the Calendar one (default, bordered, ghost). size applies to both.
The calendar props pass straight through: locale, weekStartsOn, showWeekNumbers, showOutsideDays, fixedWeeks. locale defaults to 'auto', which follows the active <I18nProvider>, so an app that already declares
its language does not repeat it here.
For a single date use DatePicker; for the calendar without an input, see Calendar. See Customization for the general contract.
03 Accessibility
Popup state on the trigger
The text input carries aria-haspopup="dialog", aria-expanded, and (while open) aria-controls pointing at the calendar, so assistive tech
reports both that a calendar exists and whether it is showing.
Keyboard
↓ opens the calendar from the field. Enter closes it while open, and commits what has been typed while it is not. Esc closes the calendar; pressed again on a field with an uncommitted draft it discards that draft rather than the selection. Grid navigation inside the calendar follows the Calendar pattern.
Both embedded buttons are named
When clearable is set the field carries two controls,
and each gets its own localized aria-label: "clear input" and "open calendar". Both
keep a visible focus-visible ring.
Typing is a first-class path
The range can be typed as well as clicked; parsing happens on blur or Enter, and a parse failure shows as the field's error.
Dismissal is separable from notification
closeOnEscape and closeOnClickOutside govern whether the popover
closes; onEscape and onClickOutside only tell you it happened.
04 API Reference
38 propsProp | Type | Default | Description | |
|---|---|---|---|---|
calendarVariant | defaultborderedghost | — | calendarVariant property | |
class | string | — | class property | |
clearable | boolean | — | clearable property | |
closeOnClickOutside | boolean | true | Whether the popover closes on outside click. | |
closeOnEscape | boolean | true | Whether the popover closes on Escape key. | |
closeOnSelect | boolean | true | Close popover after selecting both dates. | |
defaultMonth | MonthIndex | — | Default month shown when the picker opens without a value. 0–11. | |
defaultYear | number | — | Default year shown when the picker opens without a value. | |
disabled | boolean | — | disabled property | |
disabledDates | Date[] | — | disabledDates property | |
displayFormat | DateFormatOptions | — | Intl.DateTimeFormat options for displayed dates. | |
error | string | — | error property | |
fixedWeeks | boolean | — | fixedWeeks property | |
helper | string | — | helper property | |
inputVariant | outlinedfilledghostunderline | — | inputVariant property | |
isDateDisabled | (date: Date) => boolean | — | Predicate that disables specific dates. Throws are caught and logged; the date is then treated as allowed. | |
label | string | — | Label above the input. | |
locale | string | 'auto' | BCP 47 locale tag for date formatting — month names, weekday names and the
header title. Defaults to 'auto', which follows the active
<I18nProvider> locale, so an app that already declares its language does
not have to repeat it here. SSR-safe: the locale comes from context, so the
server and the client resolve the same tag (Intl with undefined would
follow the runtime and disagree across hydration). Falls back to the base
locale (en) when no provider is mounted. Pass an explicit tag
(e.g. 'de-DE', 'ja-JP') to override. | |
maxDate | Date | — | maxDate property | |
minDate | Date | — | minDate property | |
mint | MintProp | 'none' | Micro-interaction preset forwarded to the inner Input. | |
name | string | — | Shared base name for native form submission. When set, two hidden
inputs are rendered — {name}_start and {name}_end — each
carrying the serialized date, so the visible input's locale-formatted
display string is never submitted instead.
Empty range submits both halves as "". The _start / _end
convention reflects the picker's domain language — date ranges read
naturally as start/end rather than min/max. | |
onClickOutside | () => void | — | Notification only — does NOT govern close behavior. | |
onEscape | () => void | — | Notification only — does NOT govern close behavior. | |
onOpenChange | (open: boolean) => void | — | onOpenChange property | |
onValueChange | (value: DateRange | undefined) => void | — | Fires when the selected range changes. During calendar selection
the user clicks twice — once to set the start, once to set the
end. onValueChange only fires when the range is *complete*
(start ≠ end); the intermediate { start: d, end: d } state
does NOT fire this callback. Use bind:value if you need the
in-progress state. | |
placeholder | string | — | Placeholder when no range is selected. | |
preset | string | — | Apply a named preset registered on <BlocksProvider>. | |
required | boolean | — | required property | |
showOutsideDays | boolean | — | showOutsideDays property | |
showWeekNumbers | boolean | — | showWeekNumbers property | |
size | xssmmdlg +1 more | — | size property | |
slotClasses | Partial<Record<DatePickerSlots, string>> | — | Per-slot class overrides. base is the positioning wrapper the field and
the popover anchor sit in, iconButton the clear / open-calendar buttons
in the field's right-icon area. The field itself is an Input and the
overlay a Calendar; restyle those under their own names. | |
unstyled | boolean | — | Strip the default tv() classes of the wrapper AND of the Input, Popover and Calendar it renders; slotClasses and class then stand alone. | |
value | DateRange | — | Currently selected date range. Supports bind:value. | |
valueFormat | dateiso | 'date' | Format used to serialise both range halves. See
DatePickerProps.valueFormat for semantics. | |
weekStartsOn | WeekdayIndex | — | weekStartsOn property | |
...HTMLAttributes<HTMLDivElement> inherited | HTMLAttributes | — | HTML attributes (excluding: 'children') |
05 Types
Local type definitions used by this component.
Name | Kind | Category | Used by | Description | |
|---|---|---|---|---|---|
DatePickerProps | interface | props | 0 | — | |
DateRangePickerProps | interface | props | 0 | — | |
DatePickerPreset | interface | helper | 0 | — | |
DateRangePreset | interface | helper | 0 | — | |
MintProp | type | helper | 1 | — | |
MonthIndex | type | helper | 1 | Month index used by Date#getMonth(). | |
WeekdayIndex | type | helper | 1 | Weekday index used by Date#getDay(). | |
DateRange | interface | helper | 1 | An inclusive start/end date pair: a selected range, a visible window, the
range onNavigate reports. One type for Calendar, Planner and
ResourceTimeline alike.
ResourceTimeline.getRange is not this type: it also accepts local date
strings ('2026-06-16'), which a selection value must not. | |
DateFormatOptions | type | helper | 1 | — | |
DatePickerSlots | type | variant | 0 | Slot names derived from the tv() config above — single source of truth for slotClasses. | |
MintName | type | helper | 0 | A mint name: a built-in (autocompleted), 'none' to disable, or any
consumer-registered name. (string & {}) keeps the registry open — a
custom name still type-checks, it just isn't suggested. A typo therefore
also still compiles (it resolves like an unregistered custom name and
warns at runtime); the union buys completion and docs, not validation. | |
MintConfig | interface | helper | 0 | — | |
SlotNames | type | helper | 0 | Extracts the slot-name union from a slotted tv() config function — the
companion to VariantProps. The slot-mode overload returns
(props?) => { [K in keyof S]: SlotFn }, so keyof ReturnType<T> is exactly
the set of slot names a component declares in tv({ slots: … }).
Use it to type a component's slotClasses prop from the single source of
truth (its *.variants.ts) instead of hand-maintaining a parallel union
that silently drifts when a slot is added or renamed: | |
BuiltinMintName | type | helper | 0 | Built-in mint names as a literal union, so the mint prop autocompletes
across every component — the single list the hand-curated playground knobs
and docs used to drift away from. |
06 Installation
Import
import { DateRangePicker } from '@urbicon-ui/blocks';