import { YearPicker, YearPickerCell, YearPickerLayout } from '@vega-ui/react';
<YearPicker aria-label='Year' year='2026-01-01' defaultSelected='2026-01-01' style={{ width: '100%' }}>
<YearPickerLayout><YearPickerCell /></YearPickerLayout>
</YearPicker>- Browser support
- Widely available
- Chrome 111
- Edge 111
- Firefox 113
- Safari 15.4
- Safari on iOS 15.4
- Bundle size
- Oklab and OkLCh — comes from the design tokens
- Scroll snap
- scrollend — has a fallback in the code, not counted in the minimum
- scrollbar-width — covered by a -webkit- fallback, not counted in the minimum
- Resize observer
- Logical properties
- :focus-visible
- :where()
- contain
- scroll-behavior
Summary
YearPicker is the year-selection subsystem of the calendar family. It builds on DataGridPicker, supports static or scroller-based year grids, and pages years in fixed-size groups (period, 12 by default) for decade-like navigation.
Imports
import {
YearPicker,
YearPickerCell,
YearPickerItem,
YearPickerLayout,
YearPickerRow,
YearPickerRowGroup,
YearPickerScroller,
YearPickerScrollerContent,
YearPickerScrollerLayout,
createYearPickerGrid,
useYearPickerCellContext,
useYearPickerContext,
useYearPickerScrollerContext,
type YearPickerCellContextData,
type YearPickerCellProps,
type YearPickerItemProps,
type YearPickerLayoutProps,
type YearPickerProps,
type YearPickerRowGroupProps,
type YearPickerRowProps,
type YearPickerScrollerContentProps,
type YearPickerScrollerLayoutProps,
type YearPickerScrollerProps,
} from '@vega-ui/react';Exported Types
YearPickerPropsYearPickerLayoutPropsYearPickerRowGroupPropsYearPickerRowPropsYearPickerItemPropsYearPickerCellPropsYearPickerCellContextDataYearPickerScrollerPropsYearPickerScrollerContentPropsYearPickerScrollerLayoutProps
Minimal Composition
import { YearPicker, YearPickerCell, YearPickerLayout } from '@vega-ui/react';
export const locales = ['en-US', 'ru-RU', 'ar-EG', 'he-IL'];
export default function MinimalComposition({ locale = 'en-US' }: { locale?: string }) {
return (
<YearPicker aria-label='Year' locale={locale} year='2026-01-01' style={{ width: '100%' }}>
<YearPickerLayout>
<YearPickerCell />
</YearPickerLayout>
</YearPicker>
);Required Parts
YearPicker: root year-selection subsystem
Optional Parts
YearPickerLayoutYearPickerRowGroupYearPickerRowYearPickerItemYearPickerCell: the cell, placed as the template of the layoutsYearPickerScrollerYearPickerScrollerContentYearPickerScrollerLayout
Composition Order
YearPicker- either
YearPickerLayoutwith a cell template as children (<YearPickerCell />or a custom one) or custom row/item composition - optional scroller layer for paged year ranges
Variants
- Selection:
single,multiple,range - Layout: static grid or scroller-based year paging
- Period: explicit visible
year(aYYYY-MM-DDstring of any day in it) or the current year - Locale and calendar system:
localeandcalendarprops, defaulting to the runtime locale and the Gregorian calendar; eras are handled by the adapter
Edge Cases
- Large year ranges require extra testing for focus, paging, and visible context.
yearcontrols the visible period, not just selection.- Scroller mode pages by
periodyears; aperiodthat differs from the layout'srows × colsshows the wrong range.
Common Mistakes
- Treating year ranges as unbounded without checking usability.
- Letting parent calendar rules and year-picker bounds drift apart.
- Using the low-level year picker where a higher-level calendar mode switch would be clearer.
Examples
Basic
Basic: a page of years
YearPickerLayout renders a page of rows × cols years (12 by default) that starts with the year given by year. Cells are keyed by the first day of each year, so defaultSelected is '2026-01-01'. The grid has no visible title, so it gets an aria-label.
import { YearPicker, YearPickerCell, YearPickerLayout } from '@vega-ui/react';
export default function APageOfYears() {
return (
<YearPicker aria-label='Model year' locale='en-US' year='2026-01-01' defaultSelected='2026-01-01' style={{ width: '100%' }}>
<YearPickerLayout>
<YearPickerCell />
</YearPickerLayout>
</YearPicker>
);
}Controlled/Stateful
Controlled/Stateful: selected year
selected and onSelectCell make the selection controlled. The scroller pages through years, and year keeps the page of the selected year in view; null means nothing is selected.
import {
Text,
YearPicker,
YearPickerCell,
YearPickerScroller,
YearPickerScrollerContent,
YearPickerScrollerLayout,
} from '@vega-ui/react';
import { useState } from 'react';
export default function SelectedYear() {
const [year, setYear] = useState<string | null>('2024-01-01');Form/Integration
Form/Integration: year range filter
selection='range' picks a span of years. The form submits the first year as is and the end as the last day of its year, reads both back with FormData, reuses the linked hint for the empty check, and clears the state on reset.
import {
Button,
Fieldset,
FieldsetLegend,
HelperText,
Text,
YearPicker,
YearPickerCell,
YearPickerLayout,
} from '@vega-ui/react';
import { FormEvent, useId, useState } from 'react';
Layout/Overlay
Layout/Overlay: paged years with own buttons
The scroller exposes prev() and next() through apiRef, so the page can be changed from buttons outside the grid, and reports the page in view with onChangePeriod for the range label. Keep period on the scroller equal to the layout's rows × cols, and move active into the new page when the old active year scrolls away.
import {
Icon,
IconButton,
type IndexedSnapScrollerApiRef,
Text,
YearPicker,
YearPickerCell,
YearPickerScroller,
YearPickerScrollerContent,
YearPickerScrollerLayout,
} from '@vega-ui/react';
import { ChevronLeft, ChevronRight } from '@vega-ui/icons';Error
Error: under-age birth year
Here the birth year is not bounded with from / to; the app checks the age after the pick and turns the hint linked to the grid into an error. Prefer bounds when a year can never be valid (see Paged Year Range); validate when the user needs to see why.
import {
HelperText,
YearPicker,
YearPickerCell,
YearPickerScroller,
YearPickerScrollerContent,
YearPickerScrollerLayout,
} from '@vega-ui/react';
import { useId, useState } from 'react';
const MIN_AGE = 18;
const CURRENT_YEAR = 2026;Disabled
Disabled: specific years
An array of year starts disables exactly those years, here fiscal years that are already closed; to disables the future ones. The reason is written next to the grid and linked with aria-describedby.
import {
HelperText,
YearPicker,
YearPickerCell,
YearPickerLayout,
} from '@vega-ui/react';
import { useId } from 'react';
const closedYears = ['2022-01-01', '2023-01-01'];
export default function SpecificYears() {
const hintId = useId();Edge
Edge: era change in the Japanese calendar
With a calendar system from createCalendarSystem (see Calendar System Adapter), years follow that calendar. In the Japanese calendar 2019 is split: Heisei 31 runs from January 1 to April 30 and Reiwa 1 starts on May 1, so the page shows two cells for it, keyed '2019-01-01' and '2019-05-01' and labeled 平成31年 and 令和元年. Year arithmetic, paging, and from / to treat them as two years.
import { createCalendarSystem } from '@vega-ui/i18n';
import { YearPicker, YearPickerCell, YearPickerLayout } from '@vega-ui/react';
const japaneseCalendar = createCalendarSystem('japanese');
export default function EraChangeInTheJapaneseCalendar() {
return (
<YearPicker aria-label='Year' calendar={japaneseCalendar} locale='ja-JP' year='2018-01-01' style={{ width: '100%' }}>
<YearPickerLayout>
<YearPickerCell />
</YearPickerLayout>
</YearPicker>Edge: years counted from another epoch
A calendar that only renumbers Gregorian years can extend GregorianCalendar (see Extending GregorianCalendar). With the Thai Buddhist calendar the cell keyed '2026-01-01' is labeled พ.ศ. 2569 in th-TH; keys, from, and to stay Gregorian ISO days.
import { createCalendarSystem } from '@vega-ui/i18n';
import { YearPicker, YearPickerCell, YearPickerLayout } from '@vega-ui/react';
const buddhistCalendar = createCalendarSystem('buddhist');
export default function YearsCountedFromAnotherEpoch() {
return (
<YearPicker aria-label='Year' calendar={buddhistCalendar} locale='th-TH' year='2026-01-01' to='2026-01-01' style={{ width: '100%' }}>
<YearPickerLayout>
<YearPickerCell />
</YearPickerLayout>
</YearPicker>Anatomy
Overview
YearPicker wraps DataGridPicker for year values and can render either static year grids or scroller-driven paged year ranges.
Required Parts
YearPicker
Required. Owns year selection behavior and visible year context.
Optional Parts
YearPickerLayout
Optional default year-grid renderer.
YearPickerCell
Cell template. Children of YearPickerLayout and YearPickerScrollerLayout are required and rendered once per year inside a cell context read by useYearPickerCellContext(); usually <YearPickerCell />. Put custom content inside YearPickerCell to keep selection, focus, and disabled states.
YearPickerRowGroup, YearPickerRow, YearPickerItem
Optional low-level grid composition parts.
YearPickerScroller, YearPickerScrollerContent, YearPickerScrollerLayout
Optional paged year-range scroller infrastructure.
Composition Order
YearPicker- optional scroller layer
- either
YearPickerLayoutor custom row/item composition
Valid Composition Patterns
The scroller composition: YearPickerScroller › YearPickerScrollerContent › YearPickerScrollerLayout › cell template, inside YearPicker. Keep year in state and update it from onChangePeriod, so the root always knows the page in view.
import {
YearPicker,
YearPickerCell,
YearPickerScroller,
YearPickerScrollerContent,
YearPickerScrollerLayout,
} from '@vega-ui/react';
import { useState } from 'react';
export default function ValidCompositionPatterns() {
const [year, setYear] = useState('2026-01-01');
Invalid Composition Patterns
Scroller period that differs from the layout page size
The user sees one period while the paging logic thinks it is on another.
Custom layouts that ignore the provided year helpers
Year ordering and paging semantics become harder to keep correct.