Month Picker
month-picker
A month picker: a year of twelve months as a grid, with arrows on the year, that hands back a plain "YYYY-MM" string — "2026-08" — and never a day or a time zone. Reach for it wherever the thing being chosen is the month itself: a billing or subscription cycle, the period on an invoice or a statement, a monthly report or export, the target month on an expense claim or a timesheet, payroll and accounting periods, budget and forecast months, a cohort in a retention table, the month a goal or OKR is scored in, "as of" month on a snapshot, the archive month on a blog or changelog, card expiry, and the period selector above an analytics dashboard, chart or ledger — usually paired as two of them for a from/to range. Common asks it answers: "month picker", "month year picker", "monthpicker react", "select a month component", "month and year select", "billing period picker", "monthly report period selector", "choose month for dashboard", "shadcn month picker", "shadcn calendar month only", "MUI DatePicker views month equivalent", "antd DatePicker picker=month equivalent", "react-datepicker showMonthYearPicker alternative", "YYYY-MM input". shadcn/ui has no month selection anywhere: its calendar is a react-day-picker wrapper that pulls in react-day-picker and date-fns and returns a Date for a day, and captionLayout="dropdown" adds month and year dropdowns for *moving* through that grid rather than for answering with a month; the Date Picker page is that same calendar inside a popover; select, native-select and combobox are empty controls that know nothing about months. Distinct from pulld date-input, which types a full date down to the day, and from pulld calendar-heatmap, which draws a year of days rather than choosing one of its months. The value is a calendar month rather than an instant, and the component holds that line: there is deliberately no Date accessor, because handing one back means having silently picked a day and a zone — the bug that starts a billing period on the last day of the previous month for everyone west of UTC. `toMonthValue(date)` reads local fields going in (the "toISOString().slice(0, 7)" one-liner is a month early for half the planet after 22:00), `parseMonthValue` gives back { year, month } and rejects anything that is not a bare month, and the strings sort and compare as they read. Month names come from `Intl.DateTimeFormat`, so the grid is already in the reader's language with zero dependencies — no date library, no icon package, one file. Both the locale and the "which month is now" marker are resolved after mount, so a server render and the browser's first paint agree instead of tripping a hydration mismatch, and passing `locale` skips the swap entirely. It is a real `role="grid"` with a roving tabindex — one tab stop for the whole year, then arrow keys inside it. Left and right step a month and cross into the neighbouring year at the edges rather than dead-ending in December, up and down move a row and follow the `columns` prop, Home and End go to January and December (rows here are a layout choice, not a calendar week), and PageUp/PageDown hold the month and walk the years. On an RTL page left and right follow the writing direction instead of running backwards. Every cell is named with the month spelled out and its year — "August 2026", localised — because "Aug" alone stops meaning anything once the arrows have moved, and the current month carries aria-current="date". `min` and `max` take the same "YYYY-MM" strings and stop the year arrows as well as the cells, `isMonthDisabled` handles scattered holes like closed accounting periods without locking the arrows, and unavailable months are marked with aria-disabled rather than disabled so they can still be reached and read instead of being invisibly skipped. Give it a `name` and it posts with a plain form or a server action through a hidden input. Uncontrolled, controlled, or controlled on the year alone; a value set from outside pulls the grid to that year so the selection is never off screen. Styled entirely with shadcn tokens (primary, accent, input, ring, muted-foreground), so it follows light and dark mode.
Similar components
Other registries' take on month-picker.