Skip to content
Folio

Surfaces

Calendar

A month, as a grid of days.

Usage

When to reach for it

On its own for a range view or an availability grid; inside a DatePicker for choosing one.
TSX
import { Calendar } from '@misoto22/design'

Notes

A month, as a grid of days.

Wraps react-day-picker rather than building a calendar, and the reason is the long tail: a month grid is easy, and everything around it is not — the keyboard contract (arrows move a day, Page moves a month, Home and End reach the week's ends), the aria-live announcement when the month changes, week numbering, the first day of the week varying by locale, and the fact that "today" and "selected" are different states a screen reader must hear apart.

Every class is replaced rather than layered over the library's stylesheet, so nothing here depends on a CSS file this package does not control — but the library's OWN class is kept on each slot, because passing a class REPLACES it and every .rdp-* selector downstream depends on those.

The month names follow locale. The picker's own chrome — the two panels and the four chevrons that browse them — follows CalendarLabels, because locale cannot supply strings this package invented.

Anatomy

Calendar anatomy
ElementDescription
Day gridrequiredThe month: a mono weekday row over one 36px pill-cornered button per day, five rows in most months and six in some. Days from the neighbouring months are shown and dimmed rather than left as holes.
Captionrequired“September 2026” as ONE button carrying aria-expanded, not two dropdowns — that is how the date is said, and splitting it put four controls in a 250px row that also has to hold two arrows.
Month arrowsrequiredThe library’s nav, lifted out of the flow and laid across the caption at the same 36px height so the two sit at either end of the month name. Both step away while the picker is open: two ways to change the month, one of them behind a panel, is one too many.
Month and year pickerA role="dialog" drawn IN PLACE of the day grid rather than over it — twelve months in a 3×4 and up to twenty-four years in a 4×6, at exactly the size of the grid they replace. Focus moves in when it opens and Tab wraps inside it; Escape closes it and puts focus back on the caption.
Day marksrequiredToday is a ring and selected is a fill. In a range the wash lives on the CELL and the fill on the ends’ BUTTONS, which is what lets a range read as one band with round ends — and lets a one-day range, which is both ends at once, stay round.

Best practices

Do

  • Pass locale rather than trusting the page’s lang: the caption is formatted by Intl from locale.code and falls back to en-US, so a French calendar says “September” until the prop is set.
  • Re-add the library’s own class when you override a slot through classNames — yours REPLACES ours, and ours carries the .rdp-* hook every downstream selector depends on. Overriding root without rdp-root took that hook off the tree.
  • Mark availability through the classNames slots rather than by styling the day button: the wash belongs to the cell and the mark to the button, and a background put on the button flattens the round end of a range.
  • Know what widening the span costs: the year grid pages 24 at a time, so the default ten either side is one page and no paging, and a range wide enough for a birth date is a reader stepping through pages to reach 1974.
  • Translate the picker’s chrome alongside locale. locale reaches the month names and stops there, so a French calendar with the prop set still names its two panels and its four chevrons “Month and year”, “Previous year” and “Earlier years” until the CalendarLabels props are passed.

Don’t

  • Do not stretch it with a width class: it is w-fit and lays out fixed 36px columns, so w-full only replaces w-fit and leaves the same grid sitting at the start edge of a wider box.
  • Do not cut it to a fixed height. The grid is five weeks in most months and six in some, and the picker panel is sized against the grid rather than given a height of its own — a box measured on a five-week month clips the sixth.
  • Do not replace MonthCaption through components: the month-and-year picker lives inside ours, so a custom caption leaves the reader with two arrows and no way to move more than one month at a time.

Examples

a range

A month as a grid of days, with a range drawn across it. The wash lives on the CELL and the fill on the two ends' BUTTONS, which is what lets a range read as one band with round ends — and what lets a one-day range, which is both ends at once, stay round. Mark availability through the classNames slots rather than by styling the day button, or the background flattens that end. The caption is one button, not two dropdowns: that is how a date is said, and splitting it put four controls in a 250px row that also has to hold two arrows. Open it and the month and year picker is drawn in place of the grid, at exactly its size, with Escape putting focus back on the caption. labels.labelNav names the month arrows' toolbar. Left alone every calendar on a page is a nav called "Navigation bar" — react-day-picker's own string — and three examples on one page are three landmarks nobody can tell apart.

one day

One day at a time, and the two marks that must not look alike: today is a RING and selected is a FILL. One is a fact about the calendar and the other is a choice the reader made, so a system that draws both the same way leaves the reader unable to tell what they picked. Days from the neighbouring months are shown and dimmed rather than left as holes. Do not give the grid a fixed height — most months are five weeks and some are six — and do not stretch it with w-full: it is w-fit and lays out fixed 36px columns, so a width class only leaves the same grid sitting at the start edge of a wider box. For choosing a date inside a form, this belongs in a DatePicker. labels.labelNav names the month arrows' toolbar. Left alone every calendar on a page is a nav called "Navigation bar" — react-day-picker's own string — and three examples on one page are three landmarks nobody can tell apart.

Thu Sep 17 2026

disabled days

Weekends and anything before the 7th are refused, and the grid will not commit one: disabled takes matchers — a day of the week, a date range, a predicate — and dims the buttons it covers rather than removing them, so the month keeps its shape and the reader can see WHY a day is unavailable. This is also where an availability view starts: mark the cells through the classNames slots, not by styling the day button, and the marks stay compatible with the range wash. startMonth and endMonth bound how far the caption's picker will travel; the default is ten years either side, which is one page of the year grid and no paging at all. labels.labelNav names the month arrows' toolbar. Left alone every calendar on a page is a nav called "Navigation bar" — react-day-picker's own string — and three examples on one page are three landmarks nobody can tell apart.

Keyboard

Calendar keyboard interactions
KeyDoes
Moves by a day.
Moves by a week.
Page UpPage DownMoves by a month.
HomeEndJumps to the week's first or last day.
EnterSpaceChooses the focused day.

Accessibility

  • Arrows move a day, Page keys move a month, Home and End reach the week’s ends.
  • “Today” is an outline and “selected” is a fill — one is a fact about the calendar, the other a choice the reader made, and they must not look alike.
  • Month and year are one panel of buttons, not a native select: a platform list of a hundred years is a scroll rather than a choice, and it arrives styled by the operating system.
  • Tab wraps inside the open panel. It is opaque and the day grid is still mounted under it, so a Tab that left would put the reader on a day they cannot see — and past the caption that owns the Escape handler.
  • The default span is ten years either side. A birth date needs a wider one, and asks for it with startMonth.