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.