Date Range Picker
Accessible, unstyled date range picker: start/end DateFields plus a popover RangeCalendar.
|
|
| Package |
@vizejs/ui/date-range-picker |
| Maturity |
stable |
| Own the source |
vize lib pull date-range-picker |
| Requires |
context, controllable-state, date-field, id, popover, range-calendar |
| Aliases |
range picker, booking dates, check-in check-out |
| Covers |
React Aria DateRangePicker, Reka UI DateRangePicker, WAI-ARIA APG date picker dialog |
Minimal setup
Use a Vue 3.5+ project with Vue SFC compilation configured (see Vite+ integration).
vp install @vizejs/ui
Add the public import below to <script setup lang="ts">. Components provide behavior without styles; the preview uses optional Paper styles.
import { DateRangePicker, DateRangePickerRoot, DateRangePickerCalendar, DateRangePickerContent, DateRangePickerField, DateRangePickerTrigger, DateRangePickerArrow, DateRangePickerGrid, DateRangePickerHeading, DateRangePickerMonthSelect, DateRangePickerNext, DateRangePickerPrev, DateRangePickerYearSelect } from "@vizejs/ui/date-range-picker";
Or copy the source into your project with vize lib pull date-range-picker (see Source Distribution).
Try it
This is the basic example maintained with the component source, using the public package imports. The preview adds the optional Paper styles.
Open the interactive example · Styling these components
Copy the example
<!-- Hotel stay picker with check-in and check-out fields sharing one range calendar. -->
<script setup lang="ts">
import { ref } from "vue";
import {
DateRangePicker,
DateRangePickerCalendar,
DateRangePickerContent,
DateRangePickerField,
DateRangePickerTrigger,
type DateRange,
} from "@vizejs/ui/date-range-picker";
const stay = ref<DateRange | null>({
start: { year: 2026, month: 10, day: 23 },
end: { year: 2026, month: 10, day: 25 },
});
</script>
<template>
<DateRangePicker v-model="stay" start-name="checkIn" end-name="checkOut" locale="en-US">
<DateRangePickerField boundary="start" aria-label="Check-in date" />
<DateRangePickerField boundary="end" aria-label="Check-out date">
<DateRangePickerTrigger aria-label="Choose stay dates">Calendar</DateRangePickerTrigger>
</DateRangePickerField>
<DateRangePickerContent aria-label="Stay calendar">
<DateRangePickerCalendar :number-of-months="2" />
</DateRangePickerContent>
</DateRangePicker>
</template>
API
DateRangePicker
Source: src/families/date-time/date-range-picker/date-range-picker-root.vue
Props
| Prop |
Type |
Default |
Description |
id |
string | null |
undefined |
Consumer-owned base id; nullish values use a deterministic fallback. |
modelValue |
DateRange | null |
undefined |
Controlled range; undefined selects uncontrolled mode and null clears. |
defaultValue |
DateRange | null |
null |
Initial uncontrolled range. |
open |
boolean |
undefined |
Controlled popover open state; undefined selects uncontrolled mode. |
defaultOpen |
boolean |
false |
Initial uncontrolled open state. |
modal |
boolean |
false |
Make outside content inert and contain focus while open. |
min |
PlainDate | null |
undefined |
Earliest selectable date, inclusive. |
max |
PlainDate | null |
undefined |
Latest selectable date, inclusive. |
isDateUnavailable |
DateMatcher |
undefined |
Predicate for dates that cannot be selected. |
locale |
string |
undefined |
BCP 47 locale for the field and calendar; defaults to the nearest LocaleProvider. |
dir |
"ltr" | "rtl" |
undefined |
Text direction; defaults to the nearest LocaleProvider. |
today |
PlainDate | null |
undefined |
Explicit current date for the calendar; the SSR-safe way to mark today. |
now |
DateTimeNow |
undefined |
Injectable clock evaluated during setup on server and client. |
timeZone |
string |
undefined |
IANA time zone used with now and host clocks. |
disabled |
boolean |
false |
Disable the field, trigger, and calendar. |
readOnly |
boolean |
false |
Lock the value while the calendar stays browsable. |
required |
boolean |
false |
Mark the field as required. |
startName |
string |
undefined |
Hidden input name that submits the ISO start date with forms. |
endName |
string |
undefined |
Hidden input name that submits the ISO end date with forms. |
allowNonContiguousRanges |
boolean |
false |
Allow committed ranges to span unavailable dates. |
closeOnSelect |
boolean |
true |
Close the popover after a calendar selection. |
Events
| Event |
Payload |
Description |
update:modelValue |
[value: DateRange | null] |
Fired when the picker requests a new controlled range. |
change |
[value: DateRange | null, previous: DateRange | null, nativeEvent: Event | null] |
Fired after a distinct committed range with the previous range and triggering event. |
update:open |
[value: boolean] |
Fired when the picker requests a new controlled open state. |
open-change |
[value: boolean, previous: boolean, nativeEvent: Event | null] |
Fired after any distinct open-state request. |
Slots
| Slot |
Slot props |
Description |
default |
DateRangePickerSlotState |
Picker composition: start/end DateRangePickerField, trigger, content, and DateRangePickerCalendar. |
Exposed
| Member |
Type |
Description |
value |
— |
|
open |
— |
|
disabled |
— |
|
readOnly |
— |
|
state |
— |
|
setValue |
— |
|
setOpen |
— |
|
DateRangePickerRoot
Source: src/families/date-time/date-range-picker/date-range-picker-root.vue
Props
| Prop |
Type |
Default |
Description |
id |
string | null |
undefined |
Consumer-owned base id; nullish values use a deterministic fallback. |
modelValue |
DateRange | null |
undefined |
Controlled range; undefined selects uncontrolled mode and null clears. |
defaultValue |
DateRange | null |
null |
Initial uncontrolled range. |
open |
boolean |
undefined |
Controlled popover open state; undefined selects uncontrolled mode. |
defaultOpen |
boolean |
false |
Initial uncontrolled open state. |
modal |
boolean |
false |
Make outside content inert and contain focus while open. |
min |
PlainDate | null |
undefined |
Earliest selectable date, inclusive. |
max |
PlainDate | null |
undefined |
Latest selectable date, inclusive. |
isDateUnavailable |
DateMatcher |
undefined |
Predicate for dates that cannot be selected. |
locale |
string |
undefined |
BCP 47 locale for the field and calendar; defaults to the nearest LocaleProvider. |
dir |
"ltr" | "rtl" |
undefined |
Text direction; defaults to the nearest LocaleProvider. |
today |
PlainDate | null |
undefined |
Explicit current date for the calendar; the SSR-safe way to mark today. |
now |
DateTimeNow |
undefined |
Injectable clock evaluated during setup on server and client. |
timeZone |
string |
undefined |
IANA time zone used with now and host clocks. |
disabled |
boolean |
false |
Disable the field, trigger, and calendar. |
readOnly |
boolean |
false |
Lock the value while the calendar stays browsable. |
required |
boolean |
false |
Mark the field as required. |
startName |
string |
undefined |
Hidden input name that submits the ISO start date with forms. |
endName |
string |
undefined |
Hidden input name that submits the ISO end date with forms. |
allowNonContiguousRanges |
boolean |
false |
Allow committed ranges to span unavailable dates. |
closeOnSelect |
boolean |
true |
Close the popover after a calendar selection. |
Events
| Event |
Payload |
Description |
update:modelValue |
[value: DateRange | null] |
Fired when the picker requests a new controlled range. |
change |
[value: DateRange | null, previous: DateRange | null, nativeEvent: Event | null] |
Fired after a distinct committed range with the previous range and triggering event. |
update:open |
[value: boolean] |
Fired when the picker requests a new controlled open state. |
open-change |
[value: boolean, previous: boolean, nativeEvent: Event | null] |
Fired after any distinct open-state request. |
Slots
| Slot |
Slot props |
Description |
default |
DateRangePickerSlotState |
Picker composition: start/end DateRangePickerField, trigger, content, and DateRangePickerCalendar. |
Exposed
| Member |
Type |
Description |
value |
DateRange | null |
Committed range, or null until both endpoints are known. |
open |
boolean |
Whether the calendar popover is open. |
disabled |
boolean |
Whether the picker is disabled. |
readOnly |
boolean |
Whether the value is locked while the calendar can still be browsed. |
state |
PickerOpenState |
Stable open-state token. |
setValue |
(value: DateRange | null) => boolean |
Request a range; returns whether it differs. |
setOpen |
(value: boolean) => boolean |
Request an open state; returns whether it differs. |
DateRangePickerCalendar
Source: src/families/date-time/date-range-picker/date-range-picker-calendar.vue
Props
| Prop |
Type |
Default |
Description |
numberOfMonths |
number |
1 |
Number of consecutive months rendered. |
pagedNavigation |
boolean |
false |
Move month controls by numberOfMonths. |
fixedWeeks |
boolean |
false |
Always render six week rows per month. |
weekStartsOn |
Weekday |
undefined |
First day of week (0 = Sunday); defaults to the locale preference. |
weekdayFormat |
CalendarWeekdayFormat |
"short" |
Weekday column label width. |
calendar |
string |
undefined |
Intl calendar used for display labels only. |
numberingSystem |
string |
undefined |
Intl numbering system for labels. |
ariaLabel |
string |
undefined |
Accessible name for the calendar group. |
Slots
| Slot |
Slot props |
Description |
default |
CalendarSlotState |
Range calendar composition; defaults to a header plus one grid per month. |
DateRangePickerContent
Source: src/families/date-time/date-range-picker/date-range-picker-content.vue
Props
| Prop |
Type |
Default |
Description |
placement |
Placement |
"bottom-start" |
Preferred placement before collision handling. |
initialFocus |
() => HTMLElement | null | undefined |
undefined |
Initial focus target; defaults to the roving calendar day. |
ariaLabel |
string |
undefined |
Accessible dialog name. |
ariaLabelledby |
string |
undefined |
Ids that label the dialog. |
Slots
| Slot |
Slot props |
Description |
default |
Record<string, never> |
Popover body, typically DateRangePickerCalendar. |
DateRangePickerField
Source: src/families/date-time/date-range-picker/date-range-picker-field.vue
Props
| Prop |
Type |
Default |
Description |
boundary (required) |
DateRangeBoundary |
undefined (required) |
Range endpoint this field edits. |
placeholderValue |
PlainDate | null |
undefined |
Date whose segments seed arrow-key stepping from empty; defaults to the picker today or host date. |
placeholders |
FieldSegmentPlaceholders |
{ year: "yyyy", month: "mm", day: "dd" } |
Placeholder text for empty segments. |
emptyText |
string |
"Empty" |
Text announced for empty segments. |
ariaLabel |
string |
undefined |
Accessible name for the segment group. |
ariaLabelledby |
string |
undefined |
Ids that label the segment group. |
ariaDescribedby |
string |
undefined |
Ids that describe the group and every segment. |
ariaErrormessage |
string |
undefined |
Id of the validation message used while invalid. |
ariaInvalid |
boolean |
false |
Force the invalid state. |
Slots
| Slot |
Slot props |
Description |
default |
DateFieldSlotState |
Extra content after the segments, typically the picker trigger after the end field. Receives field state. |
segment |
FieldSegmentState |
Segment content. Receives the segment state; defaults to its text. |
Exposed
| Member |
Type |
Description |
focus |
— |
|
DateRangePickerTrigger
Source: src/families/overlays/popover/popover-trigger.vue
Props
| Prop |
Type |
Default |
Description |
type |
"button" | "reset" | "submit" |
"button" |
Native button submission behavior. |
disabled |
boolean |
false |
Remove the trigger from activation and sequential keyboard focus. |
ariaLabel |
string |
undefined |
Accessible name when no visible label supplies one. |
Events
| Event |
Payload |
Description |
click |
[nativeEvent: MouseEvent] |
Fired before the trigger toggles the popover. Call preventDefault() to keep state unchanged. |
Slots
| Slot |
Slot props |
Description |
default |
PopoverSlotState |
Trigger contents. Receives the current Popover state and trigger availability. |
Exposed
| Member |
Type |
Description |
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
focus |
— |
|
DateRangePickerArrow
Source: src/families/overlays/popover/popover-arrow.vue
Slots
| Slot |
Slot props |
Description |
default |
PopoverArrowSlotState |
Decorative arrow contents. Receives coordinates for optional custom drawing. |
Exposed
| Member |
Type |
Description |
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
x |
— |
|
y |
— |
|
DateRangePickerGrid
Source: src/families/date-time/calendar/calendar-grid.vue
Props
| Prop |
Type |
Default |
Description |
monthIndex |
number |
0 |
Zero-based index of the visible month this grid renders. |
showWeekNumbers |
boolean |
false |
Render a leading row header with the ISO 8601 week number of each row. |
weekNumberLabel |
string |
"Wk" |
Visible week-number column header. |
weekNumberHeader |
string |
"Week" |
Full week-number column header used as abbr. |
Slots
| Slot |
Slot props |
Description |
weekday |
CalendarWeekdaySlotState |
Weekday column header content. Receives the weekday, its labels, and column index. |
weekNumber |
CalendarWeekNumberSlotState |
Week-number row header content. Receives the ISO week and the row's days. |
day |
CalendarDayState |
Day button content. Receives the complete day state; defaults to the localized day number. |
DateRangePickerHeading
Source: src/families/date-time/calendar/calendar-heading.vue
Slots
| Slot |
Slot props |
Description |
default |
CalendarHeadingSlotState |
Heading content. Receives the localized heading and visible months. |
DateRangePickerMonthSelect
Source: src/families/date-time/calendar/calendar-month-select.vue
Props
| Prop |
Type |
Default |
Description |
ariaLabel |
string |
"Month" |
Accessible name of the native select. |
Events
| Event |
Payload |
Description |
change |
[month: number, nativeEvent: Event] |
Fired after the user picks a month, with the ISO month number. |
DateRangePickerNext
Source: src/families/date-time/calendar/calendar-next.vue
Props
| Prop |
Type |
Default |
Description |
unit |
CalendarNavigationUnit |
"month" |
Whether the control pages by month (or numberOfMonths when paged) or by year. |
ariaLabel |
string |
undefined |
Accessible name; defaults to "Next month" or "Next year". |
Events
| Event |
Payload |
Description |
click |
[nativeEvent: MouseEvent] |
Fired before navigation. Call preventDefault() to keep the view unchanged. |
Slots
| Slot |
Slot props |
Description |
default |
CalendarNavigationSlotState |
Control content. Receives the unit and whether navigation is possible. |
DateRangePickerPrev
Source: src/families/date-time/calendar/calendar-prev.vue
Props
| Prop |
Type |
Default |
Description |
unit |
CalendarNavigationUnit |
"month" |
Whether the control pages by month (or numberOfMonths when paged) or by year. |
ariaLabel |
string |
undefined |
Accessible name; defaults to "Previous month" or "Previous year". |
Events
| Event |
Payload |
Description |
click |
[nativeEvent: MouseEvent] |
Fired before navigation. Call preventDefault() to keep the view unchanged. |
Slots
| Slot |
Slot props |
Description |
default |
CalendarNavigationSlotState |
Control content. Receives the unit and whether navigation is possible. |
DateRangePickerYearSelect
Source: src/families/date-time/calendar/calendar-year-select.vue
Props
| Prop |
Type |
Default |
Description |
from |
number |
undefined |
First listed year; defaults to the min year or ten years before the view. |
to |
number |
undefined |
Last listed year; defaults to the max year or ten years after the view. |
ariaLabel |
string |
"Year" |
Accessible name of the native select. |
Events
| Event |
Payload |
Description |
change |
[year: number, nativeEvent: Event] |
Fired after the user picks a year. |
Behavior
Normative state x input -> outcome table for the DateRangePicker compound
(@vizejs/ui/date-range-picker): date-range-picker-root.vue,
date-range-picker-field.vue (one per boundary),
date-range-picker-content.vue, and date-range-picker-calendar.vue, with
DateRangePickerTrigger re-exporting PopoverTrigger. Popover, field, and
range-calendar rules follow their own contracts. Every row is proven by the
named test.
| # |
State |
Input |
Outcome |
Proven by |
| Q1 |
closed |
trigger / two day activations |
the popover opens focused on today, the range commits in order, the popover closes, and both fields plus startName/endName inputs update |
renders start and end fields and commits a two-click calendar range |
| Q2 |
complete range |
clear one field / type it back |
clearing an endpoint commits null but keeps the other draft; typing it back commits an ordered range even when entered reversed |
editing one field keeps a draft endpoint until both are known and orders the range |
| Q3 |
imperative |
expose / disabled |
setValue updates both fields and the open calendar; setValue(null) empties them; disabled pickers disable the trigger |
exposes imperative range and open control and respects disabled |
| Q4 |
SSR/hydration |
closed and open requests |
byte-identical markup in both states; an open range picker hydrates silently |
renders byte-identical closed and open range picker markup across SSR requests, hydrates an open range picker without mismatches |
Root data: data-vize-ui="date-range-picker", data-state, data-start,
data-end, data-disabled, data-readonly. Each field root carries
data-boundary.