DateRangePicker
A date range picker is a DatePicker with two ends: two segmented fields side by side, and a RangeCalendar in the popover behind them. It is the shape for a stay, a reporting period, a leave request — anything where the two dates are one answer.
import {
DateRangePicker,
DateRangePickerGroup,
DateRangePickerInput,
DateRangePickerPopover,
DateRangePickerRangeSeparator,
DateRangePickerSegment,
DateRangePickerTrigger,
} from "ropav";Two DateRangePickerInputs, marked slot="start" and slot="end", with a DateRangePickerRangeSeparator between them. Everything else matches the date picker.
date-range-picker-basic.vue
<script setup lang="ts">
import {
DateRangePicker,
DateRangePickerGroup,
DateRangePickerInput,
DateRangePickerPopover,
DateRangePickerRangeSeparator,
DateRangePickerSegment,
DateRangePickerSuffix,
DateRangePickerTrigger,
DateRangePickerTriggerIndicator,
Label,
RangeCalendar,
RangeCalendarCell,
RangeCalendarGrid,
RangeCalendarGridBody,
RangeCalendarGridHeader,
RangeCalendarHeader,
RangeCalendarHeaderCell,
RangeCalendarHeading,
RangeCalendarNavButton,
} from "ropav";
</script>
<template>
<DateRangePicker class="field" end-name="endDate" start-name="startDate">
<Label>Trip dates</Label>
<DateRangePickerGroup full-width>
<DateRangePickerInput slot="start">
<template #default="{ segment }">
<DateRangePickerSegment :segment />
</template>
</DateRangePickerInput>
<DateRangePickerRangeSeparator />
<DateRangePickerInput slot="end">
<template #default="{ segment }">
<DateRangePickerSegment :segment />
</template>
</DateRangePickerInput>
<DateRangePickerSuffix>
<DateRangePickerTrigger>
<DateRangePickerTriggerIndicator />
</DateRangePickerTrigger>
</DateRangePickerSuffix>
</DateRangePickerGroup>
<DateRangePickerPopover>
<RangeCalendar aria-label="Selected range">
<RangeCalendarHeader>
<RangeCalendarNavButton slot="previous" />
<RangeCalendarHeading />
<RangeCalendarNavButton slot="next" />
</RangeCalendarHeader>
<RangeCalendarGrid>
<RangeCalendarGridHeader>
<template #default="{ day }">
<RangeCalendarHeaderCell>{{ day }}</RangeCalendarHeaderCell>
</template>
</RangeCalendarGridHeader>
<RangeCalendarGridBody>
<template #default="{ date }">
<RangeCalendarCell :date />
</template>
</RangeCalendarGridBody>
</RangeCalendarGrid>
</RangeCalendar>
</DateRangePickerPopover>
</DateRangePicker>
</template>
<style scoped>
.field {
width: calc(var(--rp-spacing) * 80);
}
</style>Submitting two values
start-name and end-name are what the two ends submit under — two form fields, because that is what a server expects, even though the reader answered one question.
min-value, max-value and is-date-unavailable apply to the whole range, and the calendar enforces them on both ends at once.
Accessibility
- The two fields are separate controls with their own segments, so a reader tabs from the start to the end the way they would through any pair of fields.
- The separator between them is decoration. What tells a reader which end they are in is the field's own name, so label them —
aria-labelon each input where one sharedLabelabove the group is not enough. - The picker announces the range as a whole once both ends are set, which is what confirms the answer rather than leaving the reader to hold two dates in their head.
- Everything the range calendar offers is reachable from the fields alone: the popover is a convenience, never the only route.
API
DateRangePicker
| Prop | Type | Default | Description |
|---|---|---|---|
allowsNonContiguousRanges | boolean | — | Whether a range may span a date that is unavailable. |
ariaDescribedby | string | — | |
ariaLabel | string | — | |
ariaLabelledby | string | — | |
autoFocus | boolean | — | Whether the first segment takes focus as the picker appears. |
createCalendar | ((identifier: CalendarIdentifier) => Calendar) | — | Builds the calendar system for an identifier. Injectable so a build can ship fewer of them. |
defaultOpen | boolean | — | |
defaultValue | DateRange | null | — | The range the picker starts with while it owns its own value. |
endName | string | — | |
firstDayOfWeek | "fri" | "mon" | "sat" | "sun" | "thu" | "tue" | "wed" | — | The day a week starts on in the calendar, when it should not follow the locale. |
form | string | — | Id of the form the value belongs to, when it is not an ancestor. |
granularity | "day" | "hour" | "minute" | "second" | — | The smallest part of a date the picker shows. Taken from the value when unset. |
hideTimeZone | boolean | — | Whether the time zone abbreviation is hidden. |
hourCycle | 12 | 24 | — | Whether to show a 12 or 24 hour clock. Taken from the locale when unset. |
id | string | — | Id for the group around both rows of segments, which is what a label points at. |
isDateUnavailable | ((date: DateValue) => boolean) | — | Rules a date out even though it is inside the range. A prop rather than an emit: the picker calls it while validating and while drawing the calendar, and an emit has no answer to give back. |
isDisabled | boolean | — | |
isInvalid | boolean | — | Whether the picker reads as invalid regardless of what it holds. |
isOpen | boolean | — | Whether the popover is open. Set this and the caller owns it. |
isReadOnly | boolean | — | |
isRequired | boolean | — | |
locale | string | — | Overrides the locale in force, for a picker that must not follow its surroundings. |
maxValue | DateValue | null | — | |
minValue | DateValue | null | — | |
pageBehavior | "single" | "visible" | "visible" | How much the calendar moves by when it pages. |
placeholderValue | DateValue | null | — | A date whose shape the empty segments follow, which is also where the calendar opens. Defaults to today. |
shouldCloseOnSelect | boolean | (() => boolean) | true | Whether picking a range closes the popover. |
shouldForceLeadingZeros | boolean | — | Whether numbers are always padded to two digits. Taken from the locale when unset. |
startName | string | — | Name the start of the range is submitted under. |
validate | ((value: DateRange | null) => string | true | string[] | null | undefined) | — | Extra rule the range has to satisfy, beyond the bounds. |
validationBehavior | "aria" | "native" | "native" | Whether errors go through the browser or through ARIA alone. |
value | DateRange | null | — | The range the picker shows. Set this and the caller owns the value. |
The default slot receives:
| Slot prop | Type |
|---|---|
isDisabled | boolean |
isInvalid | boolean |
isOpen | boolean |
isReadOnly | boolean |
isRequired | boolean |
| Event | Payload |
|---|---|
change | DateRange | null |
focusChange | boolean |
openChange | boolean |
update:isOpen | boolean |
update:value | DateRange | null |
DateRangePickerPopover
| Prop | Type | Default | Description |
|---|---|---|---|
containerPadding | number | — | Distance kept between the popover and the edge of the viewport. |
crossOffset | number | — | Distance along the cross axis. |
isKeyboardDismissDisabled | boolean | false | |
offset | number | — | Distance along the main axis, between the popover and the group. |
placement | "bottom" | "bottom end" | "bottom left" | "bottom right" | "bottom start" | "end" | "end bottom" | "end top" | "left" | "left bottom" | "left top" | "right" | "right bottom" | "right top" | "start" | "start bottom" | "start top" | "top" | "top end" | "top left" | "top right" | "top start" | "bottom" | Where the popover sits relative to the group. |
shouldFlip | boolean | — | Whether the popover may flip to the other side when it does not fit. |
Slots: default.
DateRangePickerRangeSeparator
Slots: default.
DateRangePickerTrigger
Slots: default.
DateRangePickerTriggerIndicator
Slots: default.