Examples
Month view
A month grid with weekday headings, capped lanes and a “+2 more” affordance.
A month
A month grid with weekday headings, three lanes of all-day bars per week and a “+1 more” button on the days where something did not fit. The same events as the week view — only the range changed.
Running here
The component
Month.tsx
import { createCalendarComponents } from '@midstem/chronous-react' import type { CalendarRange, EventInput } from '@midstem/chronous-react' type EventData = { title: string } const Calendar = createCalendarComponents<EventData>() const LANE_HEIGHT = 26 const RANGE: CalendarRange = { view: 'month', currentDate: '2026-03-18', timeZone: 'Europe/Kyiv', } const EVENTS: EventInput<EventData>[] = [ { id: 'offsite', start: '2026-03-16', end: '2026-03-20', data: { title: 'Offsite' }, }, { id: 'freeze', start: '2026-03-17', end: '2026-03-19', data: { title: 'Code freeze' }, }, { id: 'audit', start: '2026-03-17', end: '2026-03-21', data: { title: 'Security audit' }, }, { id: 'retro', start: '2026-03-18', end: '2026-03-19', data: { title: 'Retro' }, }, { id: 'review', start: '2026-03-18T14:00', end: '2026-03-18T15:30', data: { title: 'Design review' }, }, ] export const Month = (): JSX.Element => ( <Calendar.Root className="calendar" range={RANGE} events={EVENTS} locale="en-GB" > <Calendar.MonthGrid className="month"> {/* MonthGrid is a flex column, so the weekday row brings its own grid */} <div className="weekdays"> <Calendar.MonthWeekdays className="weekday" /> </div> <Calendar.MonthRows className="row" maxLanes={3} laneHeight={LANE_HEIGHT}> <Calendar.MonthDays className="day"> {({ dayLabel, lanes, hiddenBars }) => ( <> <span className="day-label">{dayLabel}</span> {/* the bars float above the cells, so reserve their height */} <div style={{ height: lanes * LANE_HEIGHT }} /> <Calendar.MonthTimedEvents className="dot"> {({ event }) => event.data?.title} </Calendar.MonthTimedEvents> {hiddenBars.length > 0 && ( <button className="more" type="button"> +{hiddenBars.length} more </button> )} </> )} </Calendar.MonthDays> <Calendar.MonthAllDayEvents className="bar"> {({ event }) => event.data?.title} </Calendar.MonthAllDayEvents> </Calendar.MonthRows> </Calendar.MonthGrid> </Calendar.Root> )
The stylesheet
month.css
.month { /* seven readable columns, or a horizontal scroll rather than a squeeze */ min-width: 700px; color: #d8e5ff; } .weekdays { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); } .weekday { padding: 8px 0; text-align: center; font-size: 12px; color: #8d8da0; } .row { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); border-top: 1px solid rgba(255, 255, 255, 0.14); } .day { position: relative; min-height: 132px; padding: 4px; border-left: 1px solid rgba(255, 255, 255, 0.06); } .day[data-in-current-period='false'] { background: rgba(255, 255, 255, 0.02); color: #6b6b6b; } /* 28px is the component's own lanesTopOffset: the bars start below it */ .day-label { display: block; height: 28px; text-align: center; font-size: 12px; line-height: 28px; } .bar, .dot { padding: 0 6px; font-size: 11px; line-height: 20px; color: #0a0722; background: #8aa9f9; border-radius: 4px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } /* A bar is exactly laneHeight tall, inline — so the air between two lanes has to come out of that height rather than from a margin. */ .bar { box-sizing: border-box; border-top: 6px solid transparent; background-clip: padding-box; } .dot { margin-top: 4px; background: #aa90f8; } .more { margin-top: 4px; font-size: 11px; color: #8d8da0; background: none; border: none; cursor: pointer; }
The spacer
The bars of a week float above the cells, so each cell reserves room for them with lanes * LANE_HEIGHT — where lanes counts only what was actually drawn. Everything below that spacer, timed events included, then flows underneath the bars instead of behind them. Layout & lanes explains where those numbers come from.