Patterns
Static Year Grid
When to use:
- the useful years fit on one page, such as the next few fiscal years or recent model years
Composition notes:
- use
YearPickerLayout; the page starts with the year given byyear - set
rowsandcolsso the page holds exactly the useful years, and bound them withfromandto
Trade-offs:
- simple and stable
- no paging; use a scroller for long ranges such as birth years
import { Text, YearPicker, YearPickerCell, YearPickerLayout } from '@vega-ui/react';
import { useState } from 'react';
export default function StaticYearGrid() {
const [year, setYear] = useState<string | null>('2026-01-01');
return (
<div style={{ display: 'grid', gap: 'var(--spacing-6)', justifyItems: 'start', width: '100%' }}>
<YearPicker
aria-label='Budget year'
locale='en-US'
year='2025-01-01'Paged Year Range
When to use:
- the year range is long, such as birth years, and benefits from snap paging
Composition notes:
- use
YearPickerScroller,YearPickerScrollerContent, andYearPickerScrollerLayout - keep
periodonYearPickerScrollerequal to the layout'srows × cols; both default to 12 - start near the likely answer with
year, and bound the range withfromandto - explain the bounds next to the grid and link the text with
aria-describedby
Trade-offs:
- scales to any range
- more moving parts than a static grid
import {
HelperText,
YearPicker,
YearPickerCell,
YearPickerScroller,
YearPickerScrollerContent,
YearPickerScrollerLayout,
} from '@vega-ui/react';
import { useId, useState } from 'react';
export default function PagedYearRange() {
const [birthYear, setBirthYear] = useState<string | null>(null);Custom Grid Rendering
When to use:
- the row structure itself must change, which a cell template cannot do
Composition notes:
- build pages with
createYearPickerGridinstead of counting years by hand; pass the samecalendaras the picker startis the first year of page0andoffsetis the page index- format labels with
formatCalendarDatein that calendar (orIntl.DateTimeFormatfor Gregorian keys), never from the ISO key - when the label is shortened, as with two-digit years here, give the full year in
aria-label
Trade-offs:
- full control over markup
- labels and locale are the app's job; prefer a cell template when only the content changes
import {
YearPicker,
YearPickerItem,
YearPickerRow,
YearPickerRowGroup,
createYearPickerGrid,
} from '@vega-ui/react';
const START = '2020-01-01';
const shortFormat = new Intl.DateTimeFormat('en-US', { year: '2-digit', timeZone: 'UTC' });
const longFormat = new Intl.DateTimeFormat('en-US', { year: 'numeric', timeZone: 'UTC' });
const rows = createYearPickerGrid({ start: START, rows: 2, cols: 5 });Cell Template
When to use:
- year cells need extra content such as a count, a status, or a hint
Composition notes:
- children of
YearPickerLayoutandYearPickerScrollerLayoutare rendered once per year inside a cell context - read the year with
useYearPickerCellContext():{ col, date, fields, label };dateis the first day of the year - render the content inside
YearPickerCellso the cell keeps selection, focus, and disabled states - take year numbers from
labelorfields, never from the ISOdatestring; they differ in non-Gregorian calendars, and an era change gives two cells in one Gregorian year - look data up by
date, which is the same in every calendar system
Trade-offs:
- keeps the grid, keys, and keyboard behavior of the layout
- richer cells can crowd small sizes
import {
useYearPickerCellContext,
YearPicker,
YearPickerCell,
YearPickerLayout,
} from '@vega-ui/react';
const reports: Record<string, number> = {
'2019-01-01': 4,
'2020-01-01': 12,
'2021-01-01': 11,
'2022-01-01': 12,Comparison
Quick Decision Rule
Use YearPicker when the target value is a year or visible year range. Use MonthPicker or DayPicker when the selection target is more granular.
YearPicker vs MonthPicker
- Use
YearPickerfor years. - Use
MonthPickerfor months.
YearPicker vs DayPicker
- Use
YearPickerwhen year-level selection is the goal. - Use
DayPickerfor actual date selection.
Choose This Component When
- the product flow needs explicit year picking
- a paged year range is easier to browse than typing
Do Not Choose This Component When
- the real target is a month or full date
- a higher-level calendar mode switch would be simpler
Troubleshooting
The Visible Year Range Does Not Match The Intended Period
Symptom
The grid renders, but the year range shown is not what the product expected.
Likely Cause
Visible year, the layout start / offset, or the scroller period are out of sync.
How To Verify
- inspect the effective visible year and any current scroller index
Fix
Pass YYYY-MM-DD years, keep period equal to rows × cols, and let YearPickerScrollerLayout derive start and offset.
Large Year Paging Feels Fragile
Symptom
Paging works, but focus or visible context feels unstable across year ranges.
Likely Cause
The scroller integration is not fully synchronized with visible year context.
How To Verify
- log visible year changes while paging
- compare them with the rendered range labels
Fix
Keep period equal to the layout's page size and derive range labels from the rendered years.
Custom Layouts Break The Sense Of Calendar Continuity
Symptom
The year grid still renders, but it no longer feels like a coherent picker.
Likely Cause
Too much of the standard grid structure was replaced without preserving year-ordering cues.
How To Verify
- review the custom layout without developer context
Fix
Preserve explicit year ordering and reuse the helper-generated grids where possible.