Components · Date, time and scheduling
MiniMonth
A keyboard-navigable month grid (APG date-picker grid) with min/max, disabled days, week numbers and event dots.
Sidebar calendar with events
| Mon | Tue | Wed | Thu | Fri | Sat | Sun |
|---|---|---|---|---|---|---|
import { useState } from 'react';
import { MiniMonth, type MiniMonthDayInfo } from '@rookcom/monarch-react/scheduling';
const TODAY = '2026-11-04';
const EVENTS: Record<string, MiniMonthDayInfo> = {
'2026-11-05': { dots: ['accent'], description: '1 event' },
'2026-11-10': { dots: ['accent', 'info'], description: '2 events' },
'2026-11-18': { dots: ['warning'], description: 'Deadline' },
'2026-11-24': { dots: ['accent', 'success', 'danger'], description: '3 events' },
};
function SidebarCalendar() {
const [value, setValue] = useState<string | null>('2026-11-10');
return (
<MiniMonth
locale="en-GB"
today={TODAY}
value={value}
onValueChange={setValue}
getDayInfo={(d) => EVENTS[d]}
aria-label="Jump to date"
/>
);
}
<SidebarCalendar />Usage examples
Bookable days only, week numbers
import { MiniMonth } from '@rookcom/monarch-react/scheduling';
const TODAY = '2026-11-04';
const isWeekend = (d: string) => [0, 6].includes(new Date(`${d}T00:00:00Z`).getUTCDay());
<MiniMonth
locale="en-GB"
today={TODAY}
min={TODAY}
max="2026-12-20"
isDateDisabled={isWeekend}
showWeekNumbers
defaultValue="2026-11-12"
/>US locale (week starts on Sunday)
import { MiniMonth } from '@rookcom/monarch-react/scheduling';
const TODAY = '2026-11-04';
<MiniMonth locale="en-US" today={TODAY} defaultValue="2026-11-26" />Purpose
Shows one month (or several side by side) as a grid of days the user can move through with the keyboard and pick from. It is the calendar inside DatePicker and DateRangePicker, and works on its own as a sidebar calendar for jumping to a date. Import from @rookcom/monarch-react/scheduling.
Use when
- A sidebar or panel calendar that jumps a view to a date and marks days with events.
- Building a custom picker that needs the same grid and keyboard model as DatePicker.
- Showing availability for booking, with unavailable days blocked.
Avoid when
- The user needs to type or submit a date in a form: use DatePicker.
- Picking a period: use DateRangePicker.
- Showing events with titles and times across a month: use CalendarMonthView.
Design principles
- Dates are ISO
YYYY-MM-DDstrings throughout, so a day never shifts with the time zone. - One Tab stop for the whole grid; arrow keys, Home/End and PageUp/PageDown move inside it, as in the WAI-ARIA APG date-picker dialog.
- Selection, today and unavailable days each have a non-colour cue: a filled day, a ring and bold numeral, and a line-through (out-of-bounds days are muted and announced as unavailable).
- Locale decides names, numerals and the first day of the week; a prop can override the week start.
- Products own meaning: availability, events and holidays come in through
isDateDisabledandgetDayInfo.
API reference
import { MiniMonth } from '@rookcom/monarch-react/scheduling';| Export | Kind | Rendering | Root class |
|---|---|---|---|
MiniMonth | component | client | .mn-mini-month |
The ref goes to the root <div>; other props go there too.
range and rangePreview turn the grid into a range display for DateRangePicker; onValueChange then reports each activated day.
Generated from the TypeScript declarations of packages/react/src/components/scheduling/mini-month.
Properties
value/onValueChange (controlled) or defaultValue; month/onMonthChange (controlled) or defaultMonth for the visible month.
today defaults to the device date and refreshes at midnight. During server rendering and hydration the UTC date is used, then the device date; pass today to fix it (a workspace time zone, tests).
Pass locale when server rendering, so the server and the browser format the same names.
MiniMonth
A month calendar grid with full keyboard support, following the WAI-ARIA APG date-picker grid: arrow keys move by day and week, Home and End to the week's ends, PageUp and PageDown by month (with Shift, by year), Enter or Space selects. One roving Tab stop; `aria-selected` on the chosen days and `aria-current="date"` on today. The foundation of DatePicker and DateRangePicker, and usable on its own as a sidebar calendar.
| Prop | Type | Description |
|---|---|---|
children | ReactNode | |
className | string | |
defaultMonth | string | Any date in the initially visible month (uncontrolled). Default: the value, else today. |
defaultValue | string | null | Initially selected date (uncontrolled). |
fixedWeeks | boolean | Always six week rows, so the height never changes between months. Default true. |
focusOnMount | boolean | Focus the current day on mount (when opened in a popover). |
footer | ReactNode | Content under the grid, such as a "Today" button. |
getDayInfo | ((date: IsoDate) => MiniMonthDayInfo | undefined) | Dots and an accessible description for a day. Called for every visible day, so keep it cheap. |
id | string | |
isDateDisabled | ((date: IsoDate) => boolean) | Marks extra dates unavailable (weekends, holidays, booked days). They stay focusable but cannot be chosen. |
labels | Partial<MiniMonthLabels> | Visible and accessible text, for translation. |
locale | string | BCP 47 locale for names, numerals and the default week start. Default: the runtime's. Pass it for server rendering. |
max | string | Latest selectable date. Navigation stops at its month. |
min | string | Earliest selectable date. Navigation stops at its month. |
month | string | Any date in the first visible month (controlled). |
numberOfMonths | number | Consecutive months shown side by side. Default 1. |
onDayHover | ((date: IsoDate | null) => void) | Called when the pointer enters a day or focus moves to one, and with `null` when it leaves the grid. |
onMonthChange | ((month: IsoDate) => void) | Called with the first day of the newly visible month. |
onValueChange | ((date: IsoDate) => void) | Called with the date the user activates (click, Enter or Space). Not called for disabled dates. |
range | MiniMonthRange | null | Highlights a range instead of a single value: the start and end are selected, the days between are marked in range. For range pickers. |
rangePreview | MiniMonthRange | null | A provisional range drawn without selecting it, for example while hovering an end date. |
showOutsideDays | boolean | Show the neighbouring months' days, dimmed and inert. Default true (single month only). |
showWeekNumbers | boolean | Show ISO 8601 week numbers in a leading column. |
today | string | Today's date. Default: the device's date, updated at midnight. Pass it to use a workspace time zone. |
value | string | null | Selected date (controlled), `YYYY-MM-DD`. `null` for none. |
weekStartsOn | Weekday | First day of the week, 0 (Sunday) to 6 (Saturday). Default: from the locale. |
Variants
| Prop | Values | Description |
|---|---|---|
numberOfMonths | '1''2''…' | Consecutive months side by side; focus moves across them. Navigation buttons sit on the outer edges. |
showWeekNumbers | 'true''false' | A leading column of ISO 8601 week numbers (row headers). |
showOutsideDays | 'true''false' | Dimmed, inert days of the neighbouring months (single month only). Default true. |
fixedWeeks | 'true''false' | Always six rows, so the height never jumps. Default true. |
States
| State | Exposed as | Behaviour |
|---|---|---|
| Selected | aria-selected="true" on the cell, data-selected on the day | Filled with the accent colour. |
| Today | aria-current="date", data-today | A ring and a bold numeral. |
| Outside min/max | aria-disabled="true", data-disabled | Muted. Still focusable up to the bound so keyboard users keep their place, but cannot be chosen; navigation stops at the bound’s month. |
| Unavailable | aria-disabled="true", data-disabled, data-unavailable | Blocked by isDateDisabled inside the bounds: muted and struck through, so it is not shown by colour alone. Focusable, cannot be chosen. |
| In range / range ends | data-in-range, data-range-start, data-range-end | A tinted band between the ends, which are filled. |
| Preview | data-preview | A provisional range (hover or keyboard focus while picking an end). |
| Focus | – | Focus ring on the focused day. |
Interaction behaviour
- Clicking a day, or pressing Enter or Space on it, selects it (unless unavailable).
- Moving focus past the visible month changes the month; the previous and next buttons do too, and stop at
minandmax. - The month title is a polite live region, so month changes are announced.
- Left and Right follow the text direction (reversed in right-to-left layouts), and the chevrons mirror.
- Pointer hover and keyboard focus both report the day through
onDayHover.
Keyboard support
| Keys | Action |
|---|---|
| Tab | Enters the grid at the selected day, else today, else the first available day. |
| ← / → | Previous / next day. |
| ↑ / ↓ | Same day of the previous / next week. |
| Home / End | First / last day of the week. |
| Page Up / Page Down | Same day of the previous / next month (clamped to the month’s length). |
| Shift + Page Up / Page Down | Same day of the previous / next year. |
| Enter / Space | Select the focused day. |
Focus never moves past min or max: keys clamp to the bound.
Accessibility
- Role
- grid (one per month), with column headers, gridcells and a button per day
- Accessible name
- Each grid is labelled by its month title; each day by its full date ("Wednesday 4 November 2026") plus the
getDayInfodescription.
- Follows the WAI-ARIA APG date-picker grid: roving tabindex,
aria-selectedon cells,aria-current="date"on today. - Weekday headers carry the full name in
abbr; week numbers are row headers named "Week 45". - Event dots are decorative (
aria-hidden); their meaning must be indescription. - Outside days are hidden from assistive technology and cannot be focused.
- Day cells are 36px, 44px on touch screens that have the room.
Responsive behaviour
- One month fits a 320px screen, also with week numbers (cells shrink slightly under 352px).
- With several months the months wrap onto new lines when the container is too narrow.
- Touch screens get 44px day and navigation targets from 384px up.
Theme support
- Every colour is a Monarch token: accent for selection, accent-subtle for ranges, status colours for dots, so all themes and more-contrast modes apply.
- Forced-colours mode outlines the selection and the range, and draws dots in the text colour.
- Transitions are removed under
prefers-reduced-motion: reduce.
Do and don't
Do
- Pass
locale(andtodayif you use a workspace time zone) so server and browser agree. - Describe what dots mean in
getDayInfo().description. - Use
isDateDisabledfor unavailable days rather than hiding them.
Don't
- Do not convert ISO dates to
Datein local time to compare or format them; use the strings. - Do not put a MiniMonth in a form as the only way to enter a date: use DatePicker, which also accepts typing.
- Do not show more than three dots per day; summarise in the description instead.
Related components
- DatePicker: A form field that types or picks one date with this grid.
- DateRangePicker: Picking a period, with two months and presets.
- CalendarMonthView: A full month with event titles.
Dependencies
| Kind | Depends on |
|---|---|
| npm packages | react |
| Monarch components | None |
Date maths and formatting come from the internal Intl-based helpers (no date library).
Testing coverage
- Labelled grid per month with full weekday names in
abbr; axe clean. aria-selectedon the selection,aria-current="date"on today; one roving Tab stop.- Arrow keys, Home/End, PageUp/PageDown and Shift+PageUp/PageDown, clamping the day and crossing months.
- Click, Enter and Space select; controlled value and controlled month.
- min/max clamp keys and disable navigation;
isDateDisableddays stay focusable but cannot be chosen. - Week start from the locale or the prop; ISO week numbers as row headers.
- Event dots (max three) and their description in the accessible name.
- Range and preview highlighting;
onDayHoveron hover and leave. - Two months with focus moving between them; RTL reverses Left and Right; focusOnMount.
| Check | Covered |
|---|---|
| Axe in jsdom | Yes |
| Server rendering | Yes |
| Browser suites | Visual baselines: /__gallery/mini-month; Cross-browser axe: /components/mini-month |
Tests: packages/react/src/components/scheduling/mini-month