Month view
StableThe month as a keyboard grid: up to three events a day, “+N more” for the rest, and pattern squares on phones.
MonthView draws five or six weeks starting on the visitor’s first day of the week, with up to three event chips per day and “+N more” for the rest. It is a table with role="grid" and one Tab stop: the arrows, Page Up / Down and Home / End move the focused day, Enter opens it, and Tab reaches that day’s events. Below 640px the chips give way to small pattern squares.
October, sample events
import { useState } from 'react'import { sampleEvents, type CalendarEvent } from '@rchursin/product/data/sample'import { AgendaList, MonthView } from '@rchursin/product/calendar'import { eventTime } from '@rchursin/product/calendar/events'import { addMonths, at, formatDayLong, formatMonth, startOfDay } from '@rchursin/product/lib/dates'import { IconButton, useToast } from '@rchursin/product/ui'
/** A fixed day in the sample week, so the page reads the same on every visit */const NOW = new Date(2026, 9, 13, 10, 40)
/** * The month on its own copy of the sample events. Arrows move between days, Enter opens a * day (its agenda shows below), Tab reaches a day’s events. Click empty space in a day to add one. */export default function MonthViewExample() { const toast = useToast() const [cursor, setCursor] = useState(() => startOfDay(NOW)) const [events, setEvents] = useState(() => sampleEvents(NOW)) const [day, setDay] = useState<Date | null>(null) const title = formatMonth(cursor)Usage
import { MonthView } from '@rchursin/product/calendar'<MonthView cursor={day} onCursorChange={setDay} events={events} now={now} openEventId={null} onOpenEvent={(event, el) => openDetails(event, el)} onOpenDay={openDay} onCreateOn={openCreate} label="October 2026"/>API
MonthView props
| Prop | Type | Default | Description |
|---|---|---|---|
cursorrequired | Date | – | The focused day. Its month is shown, and it holds the tab stop. |
onCursorChangerequired | (day: Date) => void | – | Keys, focus and clicks move it. Moving into another month changes the month shown. |
eventsrequired | CalendarEvent[] | – | Events to place on the days. |
nowrequired | Date | – | Marks today. |
openEventIdrequired | string | null | – | The event whose details are open. |
onOpenEventrequired | (event: CalendarEvent, el: HTMLButtonElement) => void | – | A chip was pressed. |
onOpenDayrequired | (day: Date) => void | – | Enter or a click on a day number, or “+N more”. |
onCreateOnrequired | (day: Date) => void | – | A click on empty space in a day of this month. |
labelrequired | string | – | Name of the grid, e.g. “October 2026”. |
Accessibility
Keyboard
| Key | What it does |
|---|---|
| Left arrowRight arrow | Previous or next day (swapped in right-to-left layouts). |
| Up arrowDown arrow | The same day a week earlier or later. |
| HomeEnd | First or last day of the week; the dates locale decides where weeks start. |
| Page UpPage Down | Previous or next month, same day number (clamped: Mar 31 goes to Feb 28). |
| ShiftPage UpShiftPage Down | Previous or next year. |
| EnterSpace | Open the day. |
| Tab | From the focused day to its events and “+N more”, then out of the grid. |
- A <table role="grid"> named by label, with a hidden caption that explains the keys. Column headers carry the long weekday names.
- Each day is a button named with its state, like “Today, Tuesday, October 13, 2026, 6 events”; today also has aria-current="date".
- Only the focused day and its events are in the Tab order (a roving tab stop), so a month is one stop.
- Chips are 20px tall and 4px apart, so their 24px target areas never overlap (WCAG 2.5.8).
- The pattern squares on phones are hidden from screen readers; the day’s name already says how many events it has.
Guidelines
Do
- Keep cursor in state and update it from onCursorChange; the grid moves focus with it.
- Show the month’s name next to the grid as a live title, so paging is announced.
Don’t
- Don’t rely on the chips on phones: they hide under 640px, so make onOpenDay show the day’s events.
In use
Related
- CalendarThe whole calendar: week, day, month and agenda views, calendars told apart by pattern, and create, edit and delete.
- Week and day viewDays across, hours down: a time grid with an all-day row, overlaps side by side, and a line at the current time.
- Calendar gridOne month as an ARIA grid with the APG date-picker keys. Every picker in the kit is built on it.
- Event blocksHow an event looks in each calendar view: a block in the week grid, a bar for all-day, a chip in a month cell.
- Agenda listEvents grouped by day under headings that can stick, earliest first, each with its time, calendar pattern and people.
Source
The component’s own files, exactly as they run on this site.
/** * MonthView: the month as a grid of days, up to three events per day and "+N more" under them * for the rest (it opens the day). On phones the cells show small pattern squares instead of chips. * * Keyboard: the same grid keys as the date pickers (arrows, Page, Home / End; one Tab stop on * the current day). Enter opens the day; Tab from a day reaches its events. */import { useId, useRef, type KeyboardEvent } from "react"import { flushSync } from "react-dom"import { cx } from "../../components/cx"import type { CalendarEvent } from "../data/sample"import { dtf, endOfDay, firstDayOfWeek, formatDayLong, isSameDay, monthMatrix, startOfDay, toISODate,