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
| Sun | Mon | Tue | Wed | Thu | Fri | Sat |
|---|---|---|---|---|---|---|
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-21JSX
<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
| Sun | Mon | Tue | Wed | Thu | Fri | Sat |
|---|---|---|---|---|---|---|
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 |
| Sun | Mon | Tue | Wed | Thu | Fri | Sat |
|---|---|---|---|---|---|---|
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-23JSX
<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.| Sun | Mon | Tue | Wed | Thu | Fri | Sat |
|---|---|---|---|---|---|---|
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.| Lun | Mar | Mie | Joi | Vin | Sâm | Dum | |
|---|---|---|---|---|---|---|---|
| 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.
| 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 |
|---|---|---|---|
moderequired | '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.
- 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 agridcellnamed by its full date (Monday, October 19, 2026), witharia-selectedon the chosen day or span andaria-current="date"on today. - An out-of-bounds day is
aria-disabledand 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.calendarcalendar.navcalendar.titlecalendar.weekdaycalendar.weekNumbercalendar.cellvariants: rangeMiddle, rangeStart, rangeEndcalendar.dayvariants: today, selected, outside, blocked, unavailablecalendar.zoomItemvariants: today, selected