View on GitHub

Calendar and views

This reference covers the root calendar, shared behavior, built-in views, and custom view registration. Start with the API overview to find another contract area.

Calendar

Calendar owns shared behavior and forwards view-specific configuration under viewProps. Its generic parameters are Event, Resource, and Views.

<Calendar<Meeting, Room>
    view="week"
    events={meetings}
    timeZone="UTC"
    viewProps={{ resources: roomConfig, minTime: "08:00" }}
/>

Root props

PropTypeDefaultMeaningExample
viewCalendarBuiltInView or registered custom name"week"Selects the view and discriminates viewProps.view="month"
viewsCalendarViewRegistry{}Adds or overrides views by name.{ quarter: QuarterView }
viewPropsPartial<CalendarViewProps<typeof view>>{}Configuration owned by the selected view. Shared root props are rejected here.{ minTime: "08:00" }
classNamestringnoneAdds a class to the outer .calendar element."team-schedule"
styleCalendarStylenoneAdds React styles and typed calendar CSS variables to the outer element.{ "--calendar-time-grid-line-width": "0px" }
localeFallbackReactNodenullSuspense fallback shown while a named locale module loads.<Spinner />

All shared view props are also accepted at the root. Unknown view names throw Error: Calendar view "<name>" is not registered.

View registry types

CalendarBuiltInView is "agenda" | "day" | "month" | "time-grid" | "week". CalendarViewProps<View> extracts only the configuration owned by one built-in view. CalendarViewRegistration is a React component or a CalendarViewDefinition. CalendarViewRegistry maps names to registrations.

import type { SharedViewProps } from "@chronolanejs/react";

interface QuarterProps extends SharedViewProps<Meeting> {
    heading?: string;
}

function QuarterView({ events = [], heading = "Quarter" }: QuarterProps) {
    return <section><h2>{heading}</h2><p>{events.length} events</p></section>;
}

const views = {
    quarter: {
        component: QuarterView,
        defaultProps: { heading: "Launch quarter" }
    }
};

<Calendar<Meeting, unknown, typeof views>
    view="quarter"
    views={views}
    viewProps={{ heading: "Q3" }}
/>

Explicit viewProps override registered defaultProps. The root then owns and overrides forwarded events, backgroundEvents, locale, formatters, messages, and viewName so a custom view receives the same shared contract.

Shared view props

SharedViewProps<Event, Resource> is accepted by every direct view. Calendar accepts the same props except viewName, className, and style, which it owns at the root.

PropTypeDefaultMeaningExample
classNamestringnoneAdds a class to the direct view root."compact-agenda"
styleCalendarStylenoneAdds styles and calendar variables to the direct view root.{ height: 640 }
eventsEvent[][]Foreground events. Boundaries are normalized; extra fields are retained.[{ id: "a", start: "2026-09-14T09:00", end: "2026-09-14T10:00" }]
backgroundEventsEvent[][]Non-interactive availability/background regions. Agenda currently ignores them.[{ start: "2026-09-14T12:00", end: "2026-09-14T13:00", color: "#fee2e2" }]
dateCalendarDateInputnoneControlled navigation anchor."2026-09-14"
defaultDateCalendarDateInputcurrent timeInitial uncontrolled anchor; ignored when date is supplied.new Date(2026, 8, 14)
localeCalendarLocale"en-US"date-fns locale object or supported BCP 47-style name. Named non-default locales may suspend."en-GB"
formattersCalendarFormattersdefaultCalendarFormattersComplete registry for calendar-owned date/time rendering.{ ...defaultCalendarFormatters, time: customTime }
messagesCalendarMessagesdefaultCalendarMessagesComplete registry for visible and accessible library text.{ ...defaultCalendarMessages, next: () => "Next period" }
viewNamestringview-specificIdentity supplied to formatter/message contexts. Normally set only on a direct view or custom wrapper."work-week"
timeZonestringhost local zoneIANA zone used for calendar-field normalization and arithmetic."UTC"
minDateCalendarDateInput | nullnullInclusive earliest navigation day. Previous navigation is disabled when the anchor or active period reaches/crosses it; visible days and events are not filtered."2026-01-01"
maxDateCalendarDateInput | nullnullInclusive latest navigation day. Next navigation is disabled when the anchor or active period reaches/crosses it; visible days and events are not filtered."2026-12-31"
showControlsbooleantrueShows the built-in range header and navigation controls.false
selectedEventIdsCalendarEventId[][]Marks matching event renderers selected. The library does not update the collection.["planning", 42]
canSelectEvent(event, context) => booleanallow allRestricts semantic selection for one normalized source event occurrence.(_, { occurrence }) => occurrence.resourceId !== "locked"
canOpenEvent(event, context) => booleanallow allRestricts semantic opening for one normalized source event occurrence.(event) => event.owner.id === user.id
onDateChange(date: Date) => voidnoneFires after navigation requests a normalized anchor, in controlled and uncontrolled modes.setDate
onRangeChange(range: CalendarRange) => voidnoneFires after the visible range resolves. Month payloads also carry monthStart and monthEnd.({ days }) => fetchDays(days)
onEventSelect(event, interaction, context) => voidnoneEnables single-click and Space selection. A double-click selects once.(event) => setSelected([event.id!])
onEventOpen(event, interaction, context) => voidnoneEnables double-click, double-tap, and Enter opening.(event) => openEditor(event)
eventInteractionsCalendarEventInteractions<Event, Resource>noneAdds raw event-root interactions after semantic behavior without replacing it.{ onContextMenu: openMenu }

Selection and opening gestures are invariant: an absent callback disables only that semantic and never remaps its gesture. Event roots are focusable when a semantic keyboard action or raw onKeyDown exists, but are not represented as buttons. See Interactions and callbacks for the occurrence context and raw interaction contract.

minDate and maxDate constrain navigation anchors, not rendered data. Each value is normalized to the start of its calendar day in timeZone; the interval is inclusive and minDate must not be after maxDate.

<Calendar
    view="week"
    defaultDate="2026-09-17"
    minDate="2026-09-16"
    maxDate="2026-09-18"
    events={meetings}
/>

The week from September 14 through September 20 still renders in full. Because it partially overlaps both boundaries, both outward navigation directions are disabled. Boundaries never remove outside days or filter overlapping events.

A controlled date outside the interval is rendered unchanged. The direction farther outside is disabled; the direction back toward the interval remains available and its first navigation request is clamped directly to the nearest boundary. The clamped Date is passed to onDateChange, so the application must still update controlled state. Anchors produced by a range's custom navigation.resolveAnchor strategy are validated and clamped by the same rule.

An invalid boundary throws a contextual TypeError. Supplying minDate after maxDate throws RangeError during render.

AgendaView

AgendaView groups each event under the first visible day it overlaps. Multi-day events appear once. It accepts all shared props plus:

PropTypeDefaultMeaningExample
rangeCalendarRangeDefinition30Owns both the visible days and previous/next anchor behavior.14, "week", or { dayCount: 30, navigation: { stepDays: 30 } }
weekStartCalendarWeekStartlocale conventionOverrides the locale's first weekday for week definitions.1 for Monday
componentsAgendaComponents<Event>default renderersReplaces agenda event, day header, empty state, or navigation renderers.{ event: AgendaEvent }

MonthView

MonthView renders a complete week-aligned month grid. Each event appears on every day it overlaps.

PropTypeDefaultMeaningExample
weekStartCalendarWeekStartlocale conventionFirst weekday used for headers and month boundaries.1
showOutsideDaysbooleantrueShows events in leading/trailing cells outside the active month. Cells and headings remain visible either way.false
maxEventsPerDaynumber4Maximum visible event rows before the overflow control.3
selectedDateCalendarDateInputnoneVisually marks one day after validation and wall-clock normalization in timeZone. State is application-owned."2026-09-14"
onSelectDay(day, interaction) => voidnoneEnables day-heading buttons and reports the normalized day.(day) => setSelectedDate(day)
onShowMore({ day, events }, interaction) => voidnoneEnables the overflow limit and handles its control with all normalized events for that day.({ events }) => openList(events)
componentsMonthComponents<Event>default renderersReplaces event, day-header, or navigation renderers.{ dayHeader: MonthDay }

maxEventsPerDay must be a non-negative integer. It limits visible rows only when onShowMore is provided; otherwise every event remains visible. Month navigation always resolves the previous or next displayed month; custom range navigation belongs only to configurable agenda and time-grid ranges.

DayView, WeekView, and TimeGridView

All three accept TimeGridViewProps<Event, Resource>. DayView defaults to range="day" and viewName="day"; the day preset moves one day. WeekView defaults to range="week" and viewName="week"; the week preset moves seven days. It also keeps day columns fluid down to 96px, then scrolls horizontally. An explicit slotSizing.width or slotSizing.minWidth overrides that minimum. Explicit range values carry their own navigation. TimeGridView defaults to the week preset and viewName="time-grid" without applying a column minimum.

PropTypeDefaultMeaningExample
resourcesCalendarResourceConfig<Event, Resource>noneCreates one column per resource per visible day.{ items: rooms, getId: (room) => room.code }
groupBy"day" | "resource""day"Chooses the outer header/column grouping when resources exist."resource"
multiDayEventLayout"timed" | "dedicated""timed"Keeps foreground events that cross local midnight in hourly slots or places them in a dedicated region above the hourly grid."dedicated"
rangeCalendarRangeDefinition"week"Owns the days rendered by the grid and how previous/next resolves a new anchor."day", 5, or { dates, navigation }
weekStartCalendarWeekStartlocale conventionFirst day for "week" ranges.1
minTimeTimeOfDay"00:00"Inclusive visible wall-clock start."08:30"
maxTimeTimeOfDay | "24:00""24:00"Exclusive visible wall-clock end. 24:00 is valid only here."18:00"
slotDurationnumber60Positive integer minutes represented by one selectable slot.30
resizeStepnumberslotDurationPositive integer minutes between pointer, touch, and keyboard resize targets. Independent from visual slot size.15
labelIntervalnumberslotDurationLabel/divider cadence; must be an integer multiple of slotDuration.60
slotSizingTimeGridSlotSizingfluid width (WeekView: 96px minimum), fixed 50px heightFixed or minimum pixel dimension per slot axis. Fixed and minimum values on one axis are mutually exclusive.{ minWidth: 120, height: 48 }
selectedRangeCalendarSelectionRangenoneValidates and normalizes the controlled half-open range in timeZone, then marks every overlapping slot. The end must follow the start.{ start: "2026-09-14T09:00:00", end: "2026-09-14T10:00:00" }
canDragEvent(event, segment) => booleanallow allRestricts pointer, touch, and keyboard movement from the event surface by source event and visible segment. Evaluated only when onEventDrop exists.(_, segment) => segment.resourceId !== "locked"
onEventDrop(change: TimeGridEventDrop) => voidnoneEnables pointer/touch body dragging and focused-event keyboard movement, then reports one committed proposal. It does not mutate events.({ event, start, end }) => update(event.id, { start, end })
canResizeEvent(event, segment, edge) => booleanallow allRestricts a transparent start or end resize edge. Evaluated only when onEventResize exists.(_, segment) => segment.resourceId !== "locked"
onEventResize(change: TimeGridEventResize) => voidnoneEnables pointer, touch, and keyboard edge resizing, updates event geometry live, and reports one committed proposal.({ event, start, end }) => update(event.id, { start, end })
onSlotSelect(slot, interaction) => voidnoneEnables slot buttons, roving grid focus, and Arrow/Home/End/Page navigation, then reports the complete slot model on activation.(slot) => setRange({ start: slot.start, end: slot.end })
componentsTimeGridComponents<Event, Resource>default renderersReplaces event, slot, background, day header, resource header, or navigation renderers.{ event: ScheduleEvent }

TimeOfDay is strict zero-padded HH:mm. minTime="24:00" is invalid. TimeGridSlotSizing supports { width } or { minWidth }, independently with { height } or { minHeight }. Fixed values must be positive finite numbers; minimum values may be zero.

Resizing changes one source boundary, retains the occurrence resource, and may cross visible days on that resource. Targets follow the resizeStep scale anchored to minTime; a shorter final interval ends exactly at maxTime. The complete proposed event interval is previewed during movement. Pointer release, Enter, or blur commits once after movement. Escape, pointer cancel, and no movement produce no callback.

multiDayEventLayout="dedicated" derives placement exclusively from each half-open start/end interval; it does not add event metadata. An event is multi-day when it crosses a local calendar-day boundary, including overnight events. Dedicated bars span contiguous visible day/resource columns and split around unrelated resource columns. They retain selection/opening behavior, move by visible day/resource columns, and resize in whole calendar-day steps. Background events always remain in the hourly grid. The dedicated region is omitted when no qualifying foreground event is visible.

View definitions

CalendarViewDefinition fieldRequiredMeaningExample
componentyesReact view component receiving its own props plus the shared view contract.QuarterView
defaultPropsnoPartial defaults merged before explicit root and viewProps values.{ heading: "Quarter" }

defaultCalendarViews is the immutable built-in registry. A caller entry with the same name overrides a built-in for that Calendar. The registry accepts dynamic string lookup and returns a React element type when the name exists:

const viewName: string = getSavedViewName();
const BuiltInView = defaultCalendarViews[viewName];