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".

import { MonthPicker } from '@kalyx/react';

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

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
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. Rules are evaluated against the first day of each 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:

<form action="/api/save" method="post">
<MonthPicker name="billingMonth" defaultValue="2026-04-01T00:00:00.000Z">
<MonthPicker.Input />
<MonthPicker.Popover>
<MonthPicker.Grid />
</MonthPicker.Popover>
</MonthPicker>
<button type="submit">Save</button>
</form>

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: '',
}}
/>