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
SunMonTueWedThuFriSat
27
28
29
30
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
1
2
3
4
5
6
7
value: 2026-10-21
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.
October 2026 – November 2026
SunMonTueWedThuFriSat
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
SunMonTueWedThuFriSat
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
value: 2026-10-19 → 2026-10-23
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.
SunMonTueWedThuFriSat
27
28
29
30
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
1
2
3
4
5
6
7
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.
LunMarMieJoiVinSâmDum
40
28
29
30
1
2
3
4
41
5
6
7
8
9
10
11
42
12
13
14
15
16
17
18
43
19
20
21
22
23
24
25
44
26
27
28
29
30
31
1
45
2
3
4
5
6
7
8
سحنثرخج
٢٦
٢٧
٢٨
٢٩
٣٠
١
٢
٣
٤
٥
٦
٧
٨
٩
١٠
١١
١٢
١٣
١٤
١٥
١٦
١٧
١٨
١٩
٢٠
٢١
٢٢
٢٣
٢٤
٢٥
٢٦
٢٧
٢٨
٢٩
٣٠
٣١
١
٢
٣
٤
٥
٦
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 rather than repeated here.
PropTypeDefaultWhat it does
minstring | null—The earliest day that can be chosen, YYYY-MM-DD. The arrows stop there too.
maxstring | 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.
localestring—The language of the month and weekday names, and whose week it is. Default the reader's.
zoneZoneInput—Whose "today" is marked. Default the reader's zone.
firstDayOfWeeknumber—The day the week starts on, 0 for Sunday. Default the locale's.
months1 | 2 | 31How many months side by side. Default 1.
weekNumbersbooleanfalseA leading column of ISO week numbers.
showOutsideDaysbooleantrueWhether the days that complete the first and last weeks are drawn. Default true.
focusedDatestring—The day the keyboard is on, which decides the month on show. Controlled.
defaultFocusedDatestring—Where the keyboard starts when the calendar owns it. Default the value, or today.
onFocusedDateChangeChangeHandler<string, 'keyboard' | 'navigate'>—Fires when the keyboard or the paging buttons move to another day.
disabledbooleanfalseNothing can be chosen and nothing is focusable.
readOnlybooleanfalseCan be read and navigated, but not chosen in.
autoFocusbooleanfalseFocus the day the keyboard is on, on mount — what a picker's panel wants.
labelstring—The calendar's accessible name, which a screen reader hears before the month.
previousLabelstring'Previous'The previous-page button's name. Default 'Previous'.
nextLabelstring'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.
PropTypeDefaultWhat it does
mode'single'—One day (default), or 'range' for a start and an end.
valuestring | null—The chosen day, YYYY-MM-DD, or null. Controlled.
defaultValuestring | null—The day it starts with when the calendar owns its value.
onValueChangeChangeHandler<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.
PropTypeDefaultWhat it does
moderequired'range'—'range' is what turns the value into { start, end }.
valueDateRange | null—The chosen span, or null. Controlled.
defaultValueDateRange | null—The span it starts with when the calendar owns its value.
onValueChangeChangeHandler<DateRange | null, DateChangeReason>—Fires once both ends are chosen, start first whichever was picked first.

Calendar keyboard

KeyResult
TabOne tab stop for the grid, on the focused day; the paging buttons and the title are their own.
Left / RightThe day before or after, in the reading order.
Up / DownThe same day a week before or after.
Home / EndThe first or last day of the week.
PageUp / PageDownThe same day a month before or after; with Shift, a year.
Enter / SpaceChooses the focused day; in a range, the first press anchors and the second ends it.

Calendar accessibility

  • 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.cellvariants: rangeMiddle, rangeStart, rangeEnd
calendar.dayvariants: today, selected, outside, blocked, unavailable
calendar.zoomItemvariants: today, selected