RangeCalendar
A range calendar picks two dates that belong to each other — a stay, a reporting period. Every part mirrors the Calendar's under its own name, because the grid is the same grid; what differs is that the selection has two ends and the days between them are shown as part of it.
import {
RangeCalendar,
RangeCalendarCell,
RangeCalendarGrid,
RangeCalendarGridBody,
RangeCalendarGridHeader,
RangeCalendarHeader,
RangeCalendarHeading,
RangeCalendarNavButton,
} from "ropav";October 2026
| Sun | Mon | Tue | Wed | Thu | Fri | Sat |
|---|---|---|---|---|---|---|
range-calendar-basic.vue
<script setup lang="ts">
import {
RangeCalendar,
RangeCalendarCell,
RangeCalendarGrid,
RangeCalendarGridBody,
RangeCalendarGridHeader,
RangeCalendarHeader,
RangeCalendarHeaderCell,
RangeCalendarHeading,
RangeCalendarNavButton,
} from "ropav";
</script>
<template>
<RangeCalendar aria-label="Stay">
<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>
</template>The first press sets the start, the second the end, and hovering between the two previews the range. Pressing again starts over rather than extending, which is what stops a reader who mis-clicked from having to undo in a particular order.
Bounds and gaps
min-value and max-value bound the whole range, and is-date-unavailable rules out days inside it. A range cannot span an unavailable day: the end is capped before it, so the reader cannot select over a blackout period and only find out on submit.
visible-duration showing two months at once is the usual choice here — a range that crosses a month boundary is hard to judge one month at a time.
Accessibility
- The two ends are announced as a range rather than as two separate dates, so a reader is told what they have selected and not merely where the cursor is.
- Name the calendar. "Stay" or "Reporting period" tells a reader what the two ends mean; "Range" tells them what they already know.
- The days between the ends are shown as part of the selection and announced that way, which is what makes a range readable without seeing the highlight across the grid.
API
RangeCalendar
| Prop | Type | Default | Description |
|---|---|---|---|
allowsNonContiguousRanges | boolean | — | Whether a range may span an unavailable date. |
ariaDescribedby | string | — | |
ariaDetails | string | — | |
ariaLabel | string | — | |
ariaLabelledby | string | — | |
autoFocus | boolean | — | Whether the calendar takes focus as soon as it mounts. |
commitBehavior | "clear" | "reset" | "select" | "select" | What to do with a half-built range when the pointer or focus leaves. |
createCalendar | ((identifier: CalendarIdentifier) => Calendar) | — | Builds the calendar system for an identifier. Injectable so a build can ship fewer of them. |
defaultFocusedValue | DateValue | null | — | |
defaultValue | DateRange | null | — | |
defaultYearPickerOpen | boolean | false | |
firstDayOfWeek | "fri" | "mon" | "sat" | "sun" | "thu" | "tue" | "wed" | — | The day a week starts on, when it should not follow the locale. |
focusedValue | DateValue | null | — | |
id | string | — | |
isDateUnavailable | ((date: DateValue, anchorDate: CalendarDate | null) => boolean) | — | Rules a date out even though it is inside the range. The end the user pinned first comes along, so what is available can depend on where the range started. |
isDisabled | boolean | — | |
isInvalid | boolean | — | |
isReadOnly | boolean | — | |
isYearPickerOpen | boolean | — | Whether the year picker is open. |
maxValue | DateValue | null | — | |
minValue | DateValue | null | — | |
pageBehavior | "single" | "visible" | "visible" | |
selectionAlignment | "center" | "end" | "start" | "center" | |
value | DateRange | null | — | |
visibleDuration | DateDuration | {months: 1} | How much is on screen at once, which is also what paging moves by. |
weeksInMonth | number | — | A fixed number of week rows, so a month grid does not change height while paging. |
The default slot receives:
| Slot prop | Type |
|---|---|
isDisabled | boolean |
isInvalid | boolean |
state | AnyCalendarState |
| Event | Payload |
|---|---|
update:focusedValue | CalendarDate |
update:value | DateRange | null |
update:yearPickerOpen | boolean |
RangeCalendarCell
| Prop | Type | Default | Description |
|---|---|---|---|
date* | CalendarDate | — | The date this cell stands for. |
The default slot receives:
| Slot prop | Type |
|---|---|
date | CalendarDate |
formattedDate | string |
isDisabled | boolean |
isFocused | boolean |
isFocusVisible | boolean |
isHovered | boolean |
isInvalid | boolean |
isOutsideMonth | boolean |
isOutsideVisibleRange | boolean |
isPressed | boolean |
isSelected | boolean |
isSelectionEnd | boolean |
isSelectionStart | boolean |
isToday | boolean |
isUnavailable | boolean |
RangeCalendarCellIndicator
Slots: default.
RangeCalendarGrid
| Prop | Type | Default | Description |
|---|---|---|---|
offset | DateDuration | — | How far past the first visible date this grid starts, for a multi-month calendar. |
weekdayStyle | "long" | "narrow" | "short" | "short" | How wide the weekday names above the grid are written. |
Slots: default.
RangeCalendarGridBody
The default slot receives:
| Slot prop | Type |
|---|---|
date | CalendarDate |
RangeCalendarGridHeader
The default slot receives:
| Slot prop | Type |
|---|---|
day | string |
RangeCalendarHeader
Slots: default.
RangeCalendarHeaderCell
Slots: default.
RangeCalendarHeading
| Prop | Type | Default | Description |
|---|---|---|---|
format | CalendarHeadingFormatOptions | — | Overrides the parts the heading is written from. |
offset | DateDuration | — | How far past the first visible date this heading describes. |
RangeCalendarNavButton
| Prop | Type | Default | Description |
|---|---|---|---|
slot | "next" | "previous" | "next" | Which direction this button pages. |
Slots: default.