# Calendar

_Box Kite 2.2.6 · a markdown copy of https://www.box-kite.dev/calendar/_

A month of days on APG's grid keys — one day or a range, the week starting where the reader's starts, every name from Intl, and a day that is a YYYY-MM-DD string rather than a Date at midnight somewhere.

**Import**

```jsx
import Calendar from '@box-kite/react/components/calendar';
```

**Usage**

```jsx
<Calendar label="Delivery" value={day} onValueChange={setDay} />
```

## A range

`mode="range"` turns the value into `{ start, end }`: the first press anchors it, the pointer or the arrows preview it, and the second press ends it, whichever way round they came. A range is refused across a day nobody can choose, and Escape drops a half-picked one. The band is the cell's and the caps are the day's, so it runs unbroken along a week and mirrors in a right-to-left page.

```jsx
<Calendar mode="range" months={2} label="Stay" value={stay} onValueChange={setStay} />
```

## Days that cannot be chosen

`min` and `max` end the calendar: a day past them is not focusable and the arrows stop at the edge. `isDateUnavailable` is softer — a weekend, a holiday — reachable so it can be read out, and crossed out so nobody mistakes it for a choice.

```jsx
<Calendar label="Appointment" min="2026-10-12" max="2026-11-15"
  isDateUnavailable={(day) => [0, 6].includes(new Date(day).getUTCDay())} />
```

## The reader's week

The week starts on the locale's first day — Monday in Moldova, Sunday in the US, Saturday in much of the Gulf — and the month and weekday names are the locale's own. `weekNumbers` adds the ISO week down the side.

```jsx
<Calendar label="Calendar" locale="ro-MD" weekNumbers />
```

## The keyboard

APG's grid keys

The arrows move a day or a week, Home and End go to the week's ends, Page Up and Page Down a month, and with Shift a year. The sideways pair follows the reading order, so in a right-to-left page Left is the next day.

Press the title to zoom out

Days, then the year's months, then a run of years — the quick way back to a date of birth, without a dropdown.

## Styling

`calendar` with `calendar.nav`, `calendar.title`, `calendar.weekday`, `calendar.weekNumber`, `calendar.cell` (the range band: `rangeStart`, `rangeMiddle`, `rangeEnd`), `calendar.day` (`today`, `selected`, `outside`, `blocked`, `unavailable`) and `calendar.zoomItem`. The paint is mixed from the text colour, so it follows any theme with no second rule.

```jsx
Box.components({
  calendar: {
    children: { day: { variants: { selected: { bgColor: 'rose-600', hover: { bgColor: 'rose-700' } } } } },
  },
});
```

## Calendar props

Everything below is this component’s own. All 235 of Box’s style props work on it too, and those are on [/box](https://www.box-kite.dev/box.md) rather than repeated here.

| Prop | Type | Default | What it does |
| --- | --- | --- | --- |
| `min` | `string \| null` | — | The earliest day that can be chosen, `YYYY-MM-DD`. The arrows stop there too. |
| `max` | `string \| null` | — | The latest day that can be chosen. |
| `isDateUnavailable` | `(date: string) => boolean` | — | Days that can be reached but not chosen — a weekend, a holiday. A range is refused across one. |
| `locale` | `string` | — | The language of the month and weekday names, and whose week it is. Default the reader's. |
| `zone` | `ZoneInput` | — | Whose "today" is marked. Default the reader's zone. |
| `firstDayOfWeek` | `number` | — | The day the week starts on, 0 for Sunday. Default the locale's. |
| `months` | `1 \| 2 \| 3` | `1` | How many months side by side. Default 1. |
| `weekNumbers` | `boolean` | `false` | A leading column of ISO week numbers. |
| `showOutsideDays` | `boolean` | `true` | Whether the days that complete the first and last weeks are drawn. Default `true`. |
| `focusedDate` | `string` | — | The day the keyboard is on, which decides the month on show. Controlled. |
| `defaultFocusedDate` | `string` | — | Where the keyboard starts when the calendar owns it. Default the value, or today. |
| `onFocusedDateChange` | `ChangeHandler<string, 'keyboard' \| 'navigate'>` | — | Fires when the keyboard or the paging buttons move to another day. |
| `disabled` | `boolean` | `false` | Nothing can be chosen and nothing is focusable. |
| `readOnly` | `boolean` | `false` | Can be read and navigated, but not chosen in. |
| `autoFocus` | `boolean` | `false` | Focus the day the keyboard is on, on mount — what a picker's panel wants. |
| `label` | `string` | — | The calendar's accessible name, which a screen reader hears before the month. |
| `previousLabel` | `string` | `'Previous'` | The previous-page button's name. Default `'Previous'`. |
| `nextLabel` | `string` | `'Next'` | The next-page button's name. Default `'Next'`. |

### Calendar (one day)

One day at a time, which is the default: the value is a `YYYY-MM-DD` string or `null`.

| Prop | Type | Default | What it does |
| --- | --- | --- | --- |
| `mode` | `'single'` | — | One day (default), or `'range'` for a start and an end. |
| `value` | `string \| null` | — | The chosen day, `YYYY-MM-DD`, or `null`. Controlled. |
| `defaultValue` | `string \| null` | — | The day it starts with when the calendar owns its value. |
| `onValueChange` | `ChangeHandler<string \| null, DateChangeReason, DateChangeDetails>` | — | Fires with the day and its readings. |

### Calendar (range)

A start and an end: the first press anchors the range and the second ends it, whichever way round.

| Prop | Type | Default | What it does |
| --- | --- | --- | --- |
| `mode`required | `'range'` | — | `'range'` is what turns the value into `{ start, end }`. |
| `value` | `DateRange \| null` | — | The chosen span, or `null`. Controlled. |
| `defaultValue` | `DateRange \| null` | — | The span it starts with when the calendar owns its value. |
| `onValueChange` | `ChangeHandler<DateRange \| null, DateChangeReason>` | — | Fires once both ends are chosen, start first whichever was picked first. |

## Calendar keyboard

| Key | Result |
| --- | --- |
| `Tab` | One tab stop for the grid, on the focused day; the paging buttons and the title are their own. |
| `Left / Right` | The day before or after, in the reading order. |
| `Up / Down` | The same day a week before or after. |
| `Home / End` | The first or last day of the week. |
| `PageUp / PageDown` | The same day a month before or after; with Shift, a year. |
| `Enter / Space` | Chooses the focused day; in a range, the first press anchors and the second ends it. |

## Calendar accessibility

Implements [the WAI-ARIA APG pattern](https://www.w3.org/WAI/ARIA/apg/patterns/grid/).

- The month is a `role="grid"` named by its title, which is a polite live region, so paging says where it landed. Each day is a `gridcell` named by its full date (`Monday, October 19, 2026`), with `aria-selected` on the chosen day or span and `aria-current="date"` on today.
- An out-of-bounds day is `aria-disabled` and not focusable; an unavailable one can be reached, so it can be read, but not chosen.

Swept with axe on every release, in this state: `Calendar`. No violations, with contrast and landmark rules left to a human. Screen-reader results are not published yet.

## Calendar style tree

Every part the component draws is a node with a name, so a default can be restyled with `Box.components()` instead of a selector — and a variant is a name too.

`calendar`

`calendar.nav`

`calendar.title`

`calendar.weekday`

`calendar.weekNumber`

`calendar.cell`variants: rangeMiddle, rangeStart, rangeEnd

`calendar.day`variants: today, selected, outside, blocked, unavailable

`calendar.zoomItem`variants: today, selected

---

_Every page: https://www.box-kite.dev/llms.txt · every prop, measured: https://www.box-kite.dev/props.md_

