# MonthPicker Guides

## Patterns

### Standard Month Grid

When to use:

- users pick a month, such as a billing period, a report month, or a card expiry month

Composition notes:

- let `MonthPickerLayout` generate the grid; it renders 12 or 13 months, depending on the calendar system
- pass `locale` to the root once; the layout reads it for the month names
- remember that cells are keyed by the first day of each month

Trade-offs:

- simple and reliable
- one year at a time; the app adds year navigation (see [Controlled/Stateful](https://vega-ui.dev/components/month-picker/#controlledstateful-billing-month-with-year-navigation))

```tsx
import { MonthPicker, MonthPickerCell, MonthPickerLayout } from '@vega-ui/react';

export default function MonthsOfOneYear() {
  return (
    <MonthPicker aria-label='Start month, 2026' locale='en-US' year='2026-01-01' defaultSelected='2026-03-01' style={{ width: '100%' }}>
      <MonthPickerLayout>
        <MonthPickerCell />
      </MonthPickerLayout>
    </MonthPicker>
  );
}
```

### Dense Embedded Month Selector

When to use:

- the month selector is part of a toolbar, a filter popover, or a side panel

Composition notes:

- use four columns and `format='short'` so the year fits in three rows
- keep the root `size` small, and put the year control next to the grid
- the example keeps the full month and year in the trigger and in its accessible name

Trade-offs:

- compact
- short names are ambiguous in some locales; keep the full name in the trigger or summary

```tsx
import {
  Button,
  Icon,
  MonthPicker,
  MonthPickerCell,
  MonthPickerLayout,
  Popover,
  PopoverContent,
  PopoverTrigger,
} from '@vega-ui/react';
import { ChevronDown } from '@vega-ui/icons';
import { useState } from 'react';

const triggerFormat = new Intl.DateTimeFormat('en-US', { month: 'long', year: 'numeric', timeZone: 'UTC' });

export default function MonthFilterInAPopover() {
  const [open, setOpen] = useState(false);
  const [month, setMonth] = useState('2026-10-01');

  const pick = (next: string) => {
    setMonth(next);
    setOpen(false);
  };

  return (
    <Popover open={open} onChangeOpen={setOpen} placement='bottom-start' role='dialog'>
      <PopoverTrigger asChild>
        <Button variant='secondary' appearance='ghost' aria-label={`Statement month, ${triggerFormat.format(new Date(month))}`}>
          {triggerFormat.format(new Date(month))}
          <Icon size='sm'><ChevronDown /></Icon>
        </Button>
      </PopoverTrigger>
      <PopoverContent aria-label='Choose a statement month'>
        <MonthPicker
          aria-label='Statement month, 2026'
          locale='en-US'
          year='2026-01-01'
          to='2026-10-01'
          selected={month}
          onSelectCell={pick}
        >
          <MonthPickerLayout cols={4} format='short'>
            <MonthPickerCell />
          </MonthPickerLayout>
        </MonthPicker>
      </PopoverContent>
    </Popover>
  );
}
```

### Custom Grid Rendering

When to use:

- the row structure itself must change, which a cell template cannot do

Composition notes:

- build rows with `createMonthPickerGrid` instead of ordering months by hand; pass the same `calendar` as the picker
- format labels with `formatCalendarDate` in that calendar (or `Intl.DateTimeFormat` for Gregorian keys), never from the ISO key
- when the label is shortened, as with narrow names here, give the full name 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

```tsx
import {
  MonthPicker,
  MonthPickerItem,
  MonthPickerRow,
  MonthPickerRowGroup,
  createMonthPickerGrid,
} from '@vega-ui/react';

const YEAR = '2026-01-01';
const narrowFormat = new Intl.DateTimeFormat('en-US', { month: 'narrow', timeZone: 'UTC' });
const longFormat = new Intl.DateTimeFormat('en-US', { month: 'long', timeZone: 'UTC' });
const rows = createMonthPickerGrid({ year: YEAR, cols: 6 });

export default function CustomGridRendering() {
  return (
    <MonthPicker aria-label='Month, 2026' locale='en-US' year={YEAR} style={{ width: '100%' }}>
      <MonthPickerRowGroup>
        {rows.map(({ row, data }) => (
          <MonthPickerRow key={row} row={row}>
            {data.map(({ col, month }) => (
              <MonthPickerItem key={month} col={col} value={month} aria-label={longFormat.format(new Date(month))}>
                {narrowFormat.format(new Date(month))}
              </MonthPickerItem>
            ))}
          </MonthPickerRow>
        ))}
      </MonthPickerRowGroup>
    </MonthPicker>
  );
}
```

### Cell Template

When to use:

- month cells need extra content such as a total, a status, or a hint

Composition notes:

- children of `MonthPickerLayout` are rendered once per month inside a cell context
- read the month with `useMonthPickerCellContext()`: `{ col, date, fields, label }`; `date` is the first day of the month
- render the content inside `MonthPickerCell` so the cell keeps selection, focus, and disabled states
- take month numbers from `label` or `fields`, never from the ISO `date` string; they differ in non-Gregorian calendars
- 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

```tsx
import {
  MonthPicker,
  MonthPickerCell,
  MonthPickerLayout,
  useMonthPickerCellContext,
} from '@vega-ui/react';

const spending: Record<string, number> = {
  '2026-01-01': 1240,
  '2026-02-01': 980,
  '2026-03-01': 1415,
  '2026-04-01': 1102,
  '2026-05-01': 1630,
  '2026-06-01': 2210,
  '2026-07-01': 1875,
  '2026-08-01': 1490,
  '2026-09-01': 1320,
  '2026-10-01': 1160,
};

export const locales = ['en-US', 'ru-RU', 'ar-EG', 'he-IL'];

function SpendingMonth({ locale }: { locale: string }) {
  const money = new Intl.NumberFormat(locale, { style: 'currency', currency: 'USD', notation: 'compact' });
  const { date, label } = useMonthPickerCellContext();
  const amount = spending[date];

  if (amount === undefined) return <MonthPickerCell />;

  return (
    <MonthPickerCell aria-label={`${label}, ${money.format(amount)}`}>
      <span style={{ display: 'grid', justifyItems: 'center', lineHeight: 1.1 }}>
        <span>{label}</span>
        <small style={{ color: 'var(--label-secondary)' }}>{money.format(amount)}</small>
      </span>
    </MonthPickerCell>
  );
}

export default function CellTemplate({ locale = 'en-US' }: { locale?: string }) {
  return (
    <MonthPicker aria-label='Spending by month, 2026' locale={locale} year='2026-01-01' size='lg' to='2026-10-01' style={{ width: '100%' }}>
      <MonthPickerLayout format='short' cols={4}>
        <SpendingMonth locale={locale} />
      </MonthPickerLayout>
    </MonthPicker>
  );
}
```

## Comparison

### Quick Decision Rule

Use `MonthPicker` when users should choose a month from a grid. Use `DayPicker` for specific dates and `YearPicker` for year selection.

### `MonthPicker` vs `DayPicker`

- Use `MonthPicker` for month-level choices.
- Use `DayPicker` for day-level selection.

### `MonthPicker` vs `YearPicker`

- Use `MonthPicker` for the month within a year context.
- Use `YearPicker` when the year itself is the target.

### Choose This Component When

- the value is a month
- a dense month grid is the right interaction

### Do Not Choose This Component When

- users need a specific date or year instead of a month

## Troubleshooting

### The Month Labels Feel Inconsistent With The Rest Of The Date UI

#### Symptom

Month names in the picker do not match other date surfaces.

#### Likely Cause

Labels were hardcoded or formatted with a different locale strategy.

#### How To Verify

- compare the picker labels with the rest of the app’s date formatting

#### Fix

Use locale-driven formatting consistently and prefer the supplied layout/helper functions.

### Custom Grid Rendering Breaks Selection Clarity

#### Symptom

Months can still be selected, but users cannot tell which month is active or selected.

#### Likely Cause

Custom rendering discarded key selection-state affordances.

#### How To Verify

- test selected, active, and disabled month states in the custom layout

#### Fix

Preserve the state cues from the standard month item pattern.

### The Picker Feels Like A Generic Grid Instead Of A Calendar Primitive

#### Symptom

The UI technically works, but it no longer reads as a coherent month selector.

#### Likely Cause

The layout was customized beyond the point where month semantics stay obvious.

#### How To Verify

- review the component without developer context

#### Fix

Keep month labels and ordering explicit, and lean on the provided layout/helper APIs where possible.
