Year Range Picker
Choose an inclusive span of years from a paged year grid.
Usage
Roadmap horizon
Choose the first and last year included in a roadmap.
Choose the first and last year included in a roadmap.
<!-- Year Range Picker / Roadmap Horizon -->
<script setup lang="ts">
import { YearRangePickerRoot, YearRangePickerAnchor, YearRangePickerStartInput, YearRangePickerEndInput, YearRangePickerTrigger, YearRangePickerContent, YearRangePickerPreviousPage, YearRangePickerNextPage, YearRangePickerGrid, YearRangePickerCell } from '@sectile/vue/year-range-picker'
import { CalendarDays } from '@lucide/vue'
const roadmap = {
start: { year: 2026, month: 1, day: 1 },
end: { year: 2030, month: 1, day: 1 },
}
</script>
<template>
<YearRangePickerRoot :default-value="roadmap" v-slot="{ years }">
<YearRangePickerAnchor>
<YearRangePickerStartInput aria-label="First year" />
<YearRangePickerEndInput aria-label="Last year" />
<YearRangePickerTrigger class="catalog-picker-trigger" aria-label="Open year range picker">
<CalendarDays :size="18" aria-hidden="true" />
</YearRangePickerTrigger>
</YearRangePickerAnchor>
<YearRangePickerContent>
<YearRangePickerPreviousPage aria-label="Previous years">‹</YearRangePickerPreviousPage>
<strong>{{ years.flat()[0].year }}–{{ years.flat().at(-1).year }}</strong>
<YearRangePickerNextPage aria-label="Next years">›</YearRangePickerNextPage>
<YearRangePickerGrid>
<YearRangePickerCell v-for="year in years.flat()" :key="year.year" :value="year">
{{ year.year }}
</YearRangePickerCell>
</YearRangePickerGrid>
</YearRangePickerContent>
</YearRangePickerRoot>
</template>Bounded
Reject values outside the configured minimum and maximum.
Reject values outside the configured minimum and maximum.
<!-- Year Range Picker / Bounded -->
<script setup lang="ts">
import { YearRangePickerRoot, YearRangePickerAnchor, YearRangePickerStartInput, YearRangePickerEndInput, YearRangePickerTrigger, YearRangePickerContent, YearRangePickerPreviousPage, YearRangePickerNextPage, YearRangePickerGrid, YearRangePickerCell } from '@sectile/vue/year-range-picker'
import { CalendarDays } from '@lucide/vue'
const roadmap = {
start: { year: 2026, month: 1, day: 1 },
end: { year: 2030, month: 1, day: 1 },
}
</script>
<template>
<YearRangePickerRoot :default-value="roadmap" v-slot="{ years }">
<YearRangePickerAnchor>
<YearRangePickerStartInput aria-label="First year" />
<YearRangePickerEndInput aria-label="Last year" />
<YearRangePickerTrigger class="catalog-picker-trigger" aria-label="Open year range picker">
<CalendarDays :size="18" aria-hidden="true" />
</YearRangePickerTrigger>
</YearRangePickerAnchor>
<YearRangePickerContent>
<YearRangePickerPreviousPage aria-label="Previous years">‹</YearRangePickerPreviousPage>
<strong>{{ years.flat()[0].year }}–{{ years.flat().at(-1).year }}</strong>
<YearRangePickerNextPage aria-label="Next years">›</YearRangePickerNextPage>
<YearRangePickerGrid>
<YearRangePickerCell v-for="year in years.flat()" :key="year.year" :value="year">
{{ year.year }}
</YearRangePickerCell>
</YearRangePickerGrid>
</YearRangePickerContent>
</YearRangePickerRoot>
</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.
<!-- Year Range Picker / Controlled -->
<script setup lang="ts">
import { ref } from 'vue'
import { YearRangePickerRoot, YearRangePickerAnchor, YearRangePickerStartInput, YearRangePickerEndInput, YearRangePickerTrigger, YearRangePickerContent, YearRangePickerPreviousPage, YearRangePickerNextPage, YearRangePickerGrid, YearRangePickerCell } from '@sectile/vue/year-range-picker'
import { CalendarDays } from '@lucide/vue'
const roadmap = {
start: { year: 2026, month: 1, day: 1 },
end: { year: 2030, month: 1, day: 1 },
}
const value = ref(roadmap)
</script>
<template>
<YearRangePickerRoot v-model="value" v-slot="{ years }">
<YearRangePickerAnchor>
<YearRangePickerStartInput aria-label="First year" />
<YearRangePickerEndInput aria-label="Last year" />
<YearRangePickerTrigger class="catalog-picker-trigger" aria-label="Open year range picker">
<CalendarDays :size="18" aria-hidden="true" />
</YearRangePickerTrigger>
</YearRangePickerAnchor>
<YearRangePickerContent>
<YearRangePickerPreviousPage aria-label="Previous years">‹</YearRangePickerPreviousPage>
<strong>{{ years.flat()[0].year }}–{{ years.flat().at(-1).year }}</strong>
<YearRangePickerNextPage aria-label="Next years">›</YearRangePickerNextPage>
<YearRangePickerGrid>
<YearRangePickerCell v-for="year in years.flat()" :key="year.year" :value="year">
{{ year.year }}
</YearRangePickerCell>
</YearRangePickerGrid>
</YearRangePickerContent>
</YearRangePickerRoot>
</template>Floating positioning
This component uses the shared positioning engine. Use the live positioning example to change side, align, offsets, collision boundaries, strategy, and tracking while inspecting the resolved placement.
API
Vue package: @sectile/vue/temporal/year-range-picker
YearRangePickerRootYearRangePickerTriggerYearRangePickerAnchorYearRangePickerPortalYearRangePickerContentYearRangePickerGridYearRangePickerCellYearRangePickerStartInputYearRangePickerEndInputYearRangePickerPreviousPageYearRangePickerNextPage
Props
YearRangePickerRootProps
alignAlignment of positioned content relative to its anchor.
asElement or component rendered for this part.
asChildWhether to merge this part into its single child instead of rendering a wrapper.
avoidCollisionsWhether positioned content may flip or shift to remain visible.
collisionBoundaryBoundary used to keep positioned content visible.
collisionPaddingSpace kept between positioned content and its collision boundary.
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.
hideWhenDetachedWhether positioned content hides when its anchor leaves the layout.
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.
positionWhether the popup is positioned relative to its trigger.
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.
sidePreferred side of the anchor for positioned content.
sideOffsetDistance between positioned content and its anchor.
strategyCSS positioning strategy used for anchored content.
trackingUpdate strategy used while anchored content is open.
YearRangePickerPartProps
asElement or component rendered for this part.
asChildWhether to merge this part into its single child instead of rendering a wrapper.
YearRangePickerPortalProps
deferWhether Teleport target resolution waits until the end of the current mount or update tick.
disabledWhether interaction is unavailable.
toTeleport target for portalled content.
Slots
YearRangePickerRootSlotProps
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.
YearRangePickerCellSlotProps
currentWhether this item represents the current position.
disabledWhether interaction is unavailable.
highlightedWhether this item is highlighted for interaction.
inRangeWhether this value lies inside the selected range.
selectedWhether this item is selected.
valueCurrent value exposed by this contract.
Other types
YearRangePickerValueChangeHandler
type YearRangePickerValueChangeHandler = NonNullable<InstanceType<typeof YearRangePickerRoot>['$props']['onUpdate:modelValue']>YearRangePickerOpenChangeHandler
type YearRangePickerOpenChangeHandler = NonNullable<InstanceType<typeof YearRangePickerRoot>['$props']['onUpdate:open']>YearRangePickerHighlightedValueChangeHandler
type YearRangePickerHighlightedValueChangeHandler = NonNullable<InstanceType<typeof YearRangePickerRoot>['$props']['onUpdate:highlightedValue']>YearRangePickerValue
| Name | Type | Required |
|---|---|---|
start | DateValue | Yes |
end | DateValue | Yes |
YearPickerValue
| Name | Type | Required |
|---|---|---|
year | number | Yes |
month | number | Yes |
day | number | Yes |
Parts
Shared scope: [data-scope="year-range-picker"]. Combine it with a part selector to keep styles local to this component.
| Part | Selector | Role | Extra attributes |
|---|---|---|---|
start-input | [data-part="start-input"] | Edits the start value. | — |
end-input | [data-part="end-input"] | Edits the end value. | — |
anchor | [data-part="anchor"] | Provides the positioning reference for floating content. | — |
trigger | [data-part="trigger"] | Opens, closes, or activates the associated content. | — |
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-page | [data-part="previous-page"] | Moves to the previous page. | — |
next-page | [data-part="next-page"] | Moves to the next page. | — |
Keyboard interaction
| Key | Behavior |
|---|---|
| Arrow keys | Move between years in the active page. |
| Page Up / Page Down | Move to the previous or next page of years. |
| Enter / Space | Select the highlighted year. |
| Escape | Close the year grid without changing the value. |
Accessibility
Start and end year inputs share a paged grid and expose one inclusive year interval.
