Skip to content
Design System
Contact

Calendar

A month view with events, today marker and keyboard day navigation.

Componentsdate-fns

Playground

September and October 2026, with today marked. Click a day, or focus the grid and use the arrow keys, to see its agenda. Days outside the range are unavailable.

Preview
Demo data

September 2026

Mon
Tue
Wed
Thu
Fri
Sat
Sun

Tuesday, September 29Today

  • Standup9:30 AM
  • Initech renewal call11:00 AM
  • Design critique2:00 PM
  • Ship calendar docs5:00 PM

Props

Week starts on
Events
Density
example.tsxtsx
1import { MonthCalendar } from "@/components/ui/month-calendar"
2
3<MonthCalendar
4 events={events}
5 min={new Date(2026, 8, 1)}
6 max={new Date(2026, 9, 31)}
7 selected={day}
8 onSelect={setDay}
9/>

Day states

Today is an ink circle; the selected day is brand. Days with overlapping events are tinted and flagged, and days outside the range are unavailable.

States
  • Defaultrest
  • Hover:hover
  • Focus:focus-visible
  • Loadingaria-busy
  • Disabledoutside range
  • Errorconflict
  • Successaria-selected

Examples

At phone width the grid collapses to dots per day and the agenda sits underneath, driven by a container query rather than the viewport.

Phone width, 360px
Demo data

September 2026

Mon
Tue
Wed
Thu
Fri
Sat
Sun

Tuesday, September 29Today

  • Standup9:30 AM
  • Initech renewal call11:00 AM
  • Design critique2:00 PM
  • Ship calendar docs5:00 PM

API

PropTypeDefaultDescription
events{ id; date: string; time?: string; title; color: TagColor }[]—Dates are local yyyy-MM-dd; no time means all day. Colors come from the tag palette.
selectedDate—The selected day, whose agenda is shown. Pair with onSelect.
onSelect(day: Date) => void—Click, Enter or Space on a day inside the range.
min / maxDate—Bookable range. Days outside it are unavailable and the month buttons stop at its edges.
weekStartsOn0 | 11Sunday or Monday. Also moves Home and End.
showWeekendsbooleantrueHide Saturday and Sunday for work calendars; keyboard moves skip them.
display"chips" | "dots""chips"Event titles with a "+N more" overflow, or colored dots. Narrow containers always use dots.
density"comfortable" | "compact""comfortable"Row height and how many chips fit before the overflow (2 or 1).

Accessibility

Keyboard

Previous or next day
←or→
Same day last or next week
↑or↓
Start or end of the week
HomeorEnd
Previous or next month
PageUporPageDown
Select the focused day
EnterorSpace

Semantics and ARIA

  • The month is a role="grid" labelled by its heading, with weekday columnheaders and one gridcell per day holding a real button.
  • Roving tabindex: only the focused day is in the tab order, so Tab moves past the grid in one step and arrow keys move within it.
  • Each day's label reads the full date, today, the event count and any overlap; chips and dots are hidden from assistive tech to avoid repetition.
  • Today uses aria-current="date", the selection aria-selected, and unavailable days aria-disabled. The heading is a polite live region, so month changes are announced.
  • Colors are never the only signal: conflicts add an icon and a written warning in the agenda.