Skip to content

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.

ts
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.

Trip dates
date-range-picker-basic.vue
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-label on each input where one shared Label above 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

PropTypeDefaultDescription
allowsNonContiguousRangesboolean—Whether a range may span a date that is unavailable.
ariaDescribedbystring—
ariaLabelstring—
ariaLabelledbystring—
autoFocusboolean—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.
defaultOpenboolean—
defaultValueDateRange | null—The range the picker starts with while it owns its own value.
endNamestring—
firstDayOfWeek"fri" | "mon" | "sat" | "sun" | "thu" | "tue" | "wed"—The day a week starts on in the calendar, when it should not follow the locale.
formstring—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.
hideTimeZoneboolean—Whether the time zone abbreviation is hidden.
hourCycle12 | 24—Whether to show a 12 or 24 hour clock. Taken from the locale when unset.
idstring—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.
isDisabledboolean—
isInvalidboolean—Whether the picker reads as invalid regardless of what it holds.
isOpenboolean—Whether the popover is open. Set this and the caller owns it.
isReadOnlyboolean—
isRequiredboolean—
localestring—Overrides the locale in force, for a picker that must not follow its surroundings.
maxValueDateValue | null—
minValueDateValue | null—
pageBehavior"single" | "visible""visible"How much the calendar moves by when it pages.
placeholderValueDateValue | null—A date whose shape the empty segments follow, which is also where the calendar opens. Defaults to today.
shouldCloseOnSelectboolean | (() => boolean)trueWhether picking a range closes the popover.
shouldForceLeadingZerosboolean—Whether numbers are always padded to two digits. Taken from the locale when unset.
startNamestring—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.
valueDateRange | null—The range the picker shows. Set this and the caller owns the value.

The default slot receives:

Slot propType
isDisabledboolean
isInvalidboolean
isOpenboolean
isReadOnlyboolean
isRequiredboolean
EventPayload
changeDateRange | null
focusChangeboolean
openChangeboolean
update:isOpenboolean
update:valueDateRange | null

DateRangePickerPopover

PropTypeDefaultDescription
containerPaddingnumber—Distance kept between the popover and the edge of the viewport.
crossOffsetnumber—Distance along the cross axis.
isKeyboardDismissDisabledbooleanfalse
offsetnumber—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.
shouldFlipboolean—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.