Skip to main content

MonthPicker

Month selector. The value is the first day of the selected month in UTC-ISO form — for example, picking April 2026 yields "2026-04-01T00:00:00.000Z".

MonthPicker demo: choosing a month from the 12-month grid
Styling shown is demo-only — Kalyx ships zero CSS.
import { MonthPicker } from '@kalyx/react';

Anatomy

<MonthPicker> {/* Root — value = first day of month, UTC */}
<MonthPicker.Input /> {/* combobox <input>, parses "YYYY-MM" */}
<MonthPicker.Trigger /> {/* button that toggles the popover */}
<MonthPicker.Popover> {/* Floating-UI portal, role="dialog" */}
<MonthPicker.Grid /> {/* 3×4 grid of months, role="grid" */}
</MonthPicker.Popover>
</MonthPicker>

Input and Trigger are re-exported from DatePicker and read the MonthPicker context.

Basic usage

import { useState } from 'react';
import { MonthPicker, type ISODateString } from '@kalyx/react';

function Example() {
const [month, setMonth] = useState<ISODateString | null>(null);
return (
<MonthPicker value={month} onChange={setMonth}>
<MonthPicker.Input placeholder="YYYY-MM" />
<MonthPicker.Popover>
<MonthPicker.Grid />
</MonthPicker.Popover>
</MonthPicker>
);
}

The default displayFormat is "yyyy-MM". Override it if you prefer a different representation (e.g., "MMMM yyyy" for "April 2026").

Try it live

The live editor runs with React and all Kalyx components in scope, so import lines are omitted. Copy them in when porting to your project — see the full imports in the non-live blocks above.

Live Editor
function BasicMonthPicker() {
  const [month, setMonth] = React.useState(null);
  const headerCls = {
    header: 'kx-live-header',
    title: 'kx-live-title',
    navButton: 'kx-live-nav',
  };
  return (
    <MonthPicker value={month} onChange={setMonth}>
      <div className="kx-live-row">
        <MonthPicker.Input className="kx-live-input" placeholder="YYYY-MM" />
        <MonthPicker.Trigger className="kx-live-trigger" aria-label="Open month picker" />
      </div>
      <MonthPicker.Popover className="kx-live-popover">
        <MonthPicker.Grid
          classNames={{
            ...headerCls,
            grid: 'kx-live-month-grid',
            month: 'kx-live-my-cell',
            monthSelected: 'kx-live-my-selected',
            monthCurrent: 'kx-live-my-current',
          }}
        />
      </MonthPicker.Popover>
      <div className="kx-live-value">
        Selected: <code>{month ?? 'null'}</code>
      </div>
    </MonthPicker>
  );
}
Result
Loading...

Parts

MonthPicker reuses DatePicker's building blocks for everything except the grid:

PartSourcePurpose
MonthPicker.Rootwraps DatePicker.Rootcontrolled/uncontrolled state, displayTimezone, disabled rules, dir (RTL mirrors the month grid)
MonthPicker.Input= DatePicker.Inputtext input (combobox role)
MonthPicker.Trigger= DatePicker.Triggericon button
MonthPicker.Popover= DatePicker.PopoverFloating UI positioning
MonthPicker.Gridnew12-month grid with prev/next year navigation

Timezone

When displayTimezone is set, the committed value is the civil midnight of the selected month's first day in that zone (UTC-ISO form). The grid highlighting honors the timezone so the right month stays marked as selected even when stored as a zone-adjusted UTC string.

<MonthPicker value={month} onChange={setMonth} displayTimezone="Asia/Seoul">
<MonthPicker.Input />
<MonthPicker.Popover>
<MonthPicker.Grid />
</MonthPicker.Popover>
</MonthPicker>

Locale

Month names follow the locale prop (BCP 47). The built-in getMonthName helper uses Intl.DateTimeFormat so any locale supported by the JS runtime works without extra dependencies.

<MonthPicker locale="ko-KR">
<MonthPicker.Input />
<MonthPicker.Popover>
<MonthPicker.Grid />
</MonthPicker.Popover>
</MonthPicker>

Disabled rules

Restrict selectable months using the same DisabledRule syntax as DatePicker. A month is disabled only when the rules exclude every day in that month; a rule that blocks just the first day does not disable the remaining month.

Live Editor
function DisabledMonthPicker() {
  const [month, setMonth] = React.useState(null);
  const headerCls = {
    header: 'kx-live-header',
    title: 'kx-live-title',
    navButton: 'kx-live-nav',
  };
  return (
    <MonthPicker
      value={month}
      onChange={setMonth}
      disabled={[
        { before: '2026-01-01T00:00:00.000Z' },
        { after: '2026-12-31T00:00:00.000Z' },
      ]}
    >
      <div className="kx-live-row">
        <MonthPicker.Input className="kx-live-input" placeholder="2026 only" />
        <MonthPicker.Trigger className="kx-live-trigger" aria-label="Open month picker" />
      </div>
      <MonthPicker.Popover className="kx-live-popover">
        <MonthPicker.Grid
          classNames={{
            ...headerCls,
            grid: 'kx-live-month-grid',
            month: 'kx-live-my-cell',
            monthSelected: 'kx-live-my-selected',
            monthCurrent: 'kx-live-my-current',
            monthDisabled: 'kx-live-disabled',
          }}
        />
      </MonthPicker.Popover>
      <div className="kx-live-value">
        Selected: <code>{month ?? 'null'}</code>
      </div>
    </MonthPicker>
  );
}
Result
Loading...
<MonthPicker
value={month}
onChange={setMonth}
disabled={[
{ before: '2026-01-01T00:00:00.000Z' },
{ after: '2026-12-31T00:00:00.000Z' },
]}
>
<MonthPicker.Input placeholder="2026 only" />
<MonthPicker.Popover>
<MonthPicker.Grid />
</MonthPicker.Popover>
</MonthPicker>

Uncontrolled

For simple forms where you don't need React state:

<MonthPicker defaultValue="2026-04-01T00:00:00.000Z">
<MonthPicker.Input name="billingMonth" />
<MonthPicker.Popover>
<MonthPicker.Grid />
</MonthPicker.Popover>
</MonthPicker>

MonthPicker.Input inherits DatePicker.Input's native form contract. Passing name renders a hidden input containing the month-start UTC ISO value; the visible formatted input is not submitted under that name.

Event callbacks

PropSignatureFires when
onChange(value: ISODateString | null) => voidA month is committed (click or input typed).
onOpenChange(isOpen: boolean) => voidThe popover opens or closes.
onCalendarNavigate(viewMonth: ISODateString) => voidThe grid navigates to a different year.

Props

MonthPicker Root accepts the same props as DatePicker.Root. The only difference is the default displayFormat — otherwise disabled, readOnly, weekStartsOn, locale, displayTimezone, labels, adapter, onOpenChange, and onCalendarNavigate all behave identically. See DatePicker for the full reference.

Grid classNames

<MonthPicker.Grid
classNames={{
root: '',
header: '',
title: '',
navButton: '',
grid: '',
gridRow: '',
month: '',
monthSelected: '',
monthCurrent: '',
monthDisabled: '',
}}
/>

Each month cell emits data-selected, data-current, and data-focused (active-only). See Styling.