Range Calendar
Choose an inclusive date range directly in an always-visible calendar grid.
Usage
Booking
Choose an inclusive start and end date for a reservation.
Choose an inclusive start and end date for a reservation.
<!-- Range Calendar / Booking -->
<script setup lang="ts">
import { RangeCalendarRoot, RangeCalendarContent, RangeCalendarPreviousMonth, RangeCalendarNextMonth, RangeCalendarGrid, RangeCalendarCell } from '@sectile/vue/range-calendar'
const stay = {
start: { year: 2026, month: 8, day: 22 },
end: { year: 2026, month: 8, day: 25 },
}
</script>
<template>
<RangeCalendarRoot :default-value="stay" v-slot="{ dates, view }">
<RangeCalendarContent>
<header>
<RangeCalendarPreviousMonth aria-label="Previous month">‹</RangeCalendarPreviousMonth>
<strong>{{ view.year }}-{{ String(view.month).padStart(2, '0') }}</strong>
<RangeCalendarNextMonth aria-label="Next month">›</RangeCalendarNextMonth>
</header>
<RangeCalendarGrid>
<RangeCalendarCell v-for="day in dates.flat()" :key="JSON.stringify(day)" :value="day">
{{ day.day }}
</RangeCalendarCell>
</RangeCalendarGrid>
</RangeCalendarContent>
</RangeCalendarRoot>
</template>Bounded
Reject values outside the configured minimum and maximum.
Reject values outside the configured minimum and maximum.
<!-- Range Calendar / Bounded -->
<script setup lang="ts">
import { RangeCalendarRoot, RangeCalendarContent, RangeCalendarPreviousMonth, RangeCalendarNextMonth, RangeCalendarGrid, RangeCalendarCell } from '@sectile/vue/range-calendar'
const stay = {
start: { year: 2026, month: 8, day: 22 },
end: { year: 2026, month: 8, day: 25 },
}
</script>
<template>
<RangeCalendarRoot :default-value="stay" v-slot="{ dates, view }">
<RangeCalendarContent>
<header>
<RangeCalendarPreviousMonth aria-label="Previous month">‹</RangeCalendarPreviousMonth>
<strong>{{ view.year }}-{{ String(view.month).padStart(2, '0') }}</strong>
<RangeCalendarNextMonth aria-label="Next month">›</RangeCalendarNextMonth>
</header>
<RangeCalendarGrid>
<RangeCalendarCell v-for="day in dates.flat()" :key="JSON.stringify(day)" :value="day">
{{ day.day }}
</RangeCalendarCell>
</RangeCalendarGrid>
</RangeCalendarContent>
</RangeCalendarRoot>
</template>Controlled
Let the parent own the current value and apply accepted changes back to the component.
Let the parent own the current value and apply accepted changes back to the component.
<!-- Range Calendar / Controlled -->
<script setup lang="ts">
import { ref } from 'vue'
import { RangeCalendarRoot, RangeCalendarContent, RangeCalendarPreviousMonth, RangeCalendarNextMonth, RangeCalendarGrid, RangeCalendarCell } from '@sectile/vue/range-calendar'
const stay = {
start: { year: 2026, month: 8, day: 22 },
end: { year: 2026, month: 8, day: 25 },
}
const value = ref(stay)
</script>
<template>
<RangeCalendarRoot v-model="value" v-slot="{ dates, view }">
<RangeCalendarContent>
<header>
<RangeCalendarPreviousMonth aria-label="Previous month">‹</RangeCalendarPreviousMonth>
<strong>{{ view.year }}-{{ String(view.month).padStart(2, '0') }}</strong>
<RangeCalendarNextMonth aria-label="Next month">›</RangeCalendarNextMonth>
</header>
<RangeCalendarGrid>
<RangeCalendarCell v-for="day in dates.flat()" :key="JSON.stringify(day)" :value="day">
{{ day.day }}
</RangeCalendarCell>
</RangeCalendarGrid>
</RangeCalendarContent>
</RangeCalendarRoot>
</template>API
Vue package: @sectile/vue/temporal/range-calendar
RangeCalendarRootRangeCalendarContentRangeCalendarGridRangeCalendarCellRangeCalendarPreviousMonthRangeCalendarNextMonthRangeCalendarPreviousYearRangeCalendarNextYear
Props
RangeCalendarRootProps
asElement or component rendered for this part.
asChildWhether to merge this part into its single child instead of rendering a wrapper.
defaultHighlightedValueInitially highlighted value for uncontrolled state.
defaultOpenInitial uncontrolled open state.
defaultValueInitial value used when the component owns its state.
defaultViewInitial calendar or picker view.
disabledWhether interaction is unavailable.
highlightedValueValue currently highlighted for keyboard interaction.
labelAccessible name announced for the control.
modelValueCurrent value when state is controlled by the parent.
openWhether the associated popup or disclosure is open.
policiesBehavior policies that customize validation, movement, or selection.
readonlyWhether the value can be inspected but not changed.
referenceDateDeterministic civil date used when a calendar has no selected or highlighted value.
requiredWhether the control must contain a valid value before submission.
RangeCalendarPartProps
asElement or component rendered for this part.
asChildWhether to merge this part into its single child instead of rendering a wrapper.
Slots
RangeCalendarRootSlotProps
datesDates projected by the active view.
disabledWhether interaction is unavailable.
highlightedValueValue currently highlighted for interaction.
monthsMonths projected by the active view.
openWhether the associated popup or disclosure is open.
readonlyWhether the value can be inspected but not changed.
valueCurrent value exposed by this contract.
viewCurrent calendar anchor.
viewModeActive calendar view mode.
yearsYears projected by the active view.
RangeCalendarCellSlotProps
disabledWhether interaction is unavailable.
highlightedWhether this item is highlighted for interaction.
inRangeWhether this value lies inside the selected range.
outsideMonthWhether this date belongs to an adjacent month.
selectedWhether this item is selected.
valueCurrent value exposed by this contract.
Other types
RangeCalendarValueChangeHandler
type RangeCalendarValueChangeHandler = NonNullable<InstanceType<typeof RangeCalendarRoot>['$props']['onUpdate:modelValue']>RangeCalendarOpenChangeHandler
type RangeCalendarOpenChangeHandler = NonNullable<InstanceType<typeof RangeCalendarRoot>['$props']['onUpdate:open']>RangeCalendarHighlightedValueChangeHandler
type RangeCalendarHighlightedValueChangeHandler = NonNullable<InstanceType<typeof RangeCalendarRoot>['$props']['onUpdate:highlightedValue']>DateRange
| Name | Type | Required |
|---|---|---|
start | DateValue | Yes |
end | DateValue | Yes |
DateValue
| Name | Type | Required |
|---|---|---|
year | number | Yes |
month | number | Yes |
day | number | Yes |
Parts
Shared scope: [data-scope="range-calendar"]. Combine it with a part selector to keep styles local to this component.
| Part | Selector | Role | Extra attributes |
|---|---|---|---|
content | [data-part="content"] | Contains the component content shown for the active state. | — |
grid | [data-part="grid"] | Groups cells into a navigable two-dimensional structure. | — |
cell | [data-part="cell"] | Represents one navigable or selectable grid value. | — |
previous-month | [data-part="previous-month"] | Moves to the previous month. | — |
next-month | [data-part="next-month"] | Moves to the next month. | — |
previous-year | [data-part="previous-year"] | Moves to the previous year. | — |
next-year | [data-part="next-year"] | Moves to the next year. | — |
Keyboard interaction
| Key | Behavior |
|---|---|
| Arrow keys | Move the highlighted date by one day or one week. |
| Home / End | Move to the start or end of the week. |
| Page Up / Page Down | Move by one month; hold Shift to move by one year. |
| Enter / Space | Select the highlighted date. |
| Escape | Close the calendar without selecting another date. |
Accessibility
The always-visible grid exposes both range endpoints and every date between them without a popup trigger.
