Skip to content

Event Calendar

Displays scheduled events across day, week, and month views, making it easy for users to create and manage appointments.

58.8 KB
•
Updated on August 21, 2026

The demo above is the whole feature surface in one example: every view (month, week, day, N-days, agenda, and the resource time grid), a multi-day all-day bar, custom chips via renderEvent, and a tabbed Settings panel driving view settings, time-grid options, and interactions as controlled props - with a reset back to defaults.

The event-calendar package separates the calendar engine from the calendar UI. A subscribable store (useEventCalendarState) owns events, view, date, selection, and interactions with controlled and uncontrolled modes for every state pair; the shipped view components (month, week, day, N-days, agenda, and a resource day grid) render from that store through fine-grained selector hooks. Events are yours: the calendar never persists anything, it proposes changes through onEventUpdate and you accept, adjust, or reject them.

The composition contract is a provider plus slots: <EventCalendar> wraps <EventCalendarNav />, an optional <EventCalendarToolbar />, and <EventCalendarContent />. Recurrence (an RFC 5545 subset, structured rules or raw RRULE strings), display time zones via @date-fns/tz, pointer-based drag, resize, and drag-create, and per-key i18n overrides are built in.

Installation

Bash

Usage

Code

Pass defaultEvents for uncontrolled state or events + onEventsChange for controlled state; the same pairing exists for view, date, dayCount, selection, interactions, and viewSettings. In the default scrollMode="contained" the calendar fills its container and scrolls internally, so give the root an explicit height (the examples use h-[560px]). Every timing change, whether from a drag, a resize, or api.updateEvent, funnels through onEventUpdate: return false to reject and revert, return nothing (or true) to accept, or return { start, end, allDay } to accept with an adjustment, then persist to your backend from onEventsChange or inside onEventUpdate itself. Use onRangeChange to fetch remote events for the visible range, and apiRef (or a hoisted useEventCalendarState instance passed as calendar) for imperative control from outside the tree.

For more variations, browse the Event Calendar components.

API Reference

EventCalendar

The root provider and container. It creates (or adopts) the calendar instance, provides it via context, and renders a flex-column div (customizable through render). Besides the props below, it accepts every option and callback listed under State options and Callbacks, and every display prop listed under View configuration.


EventCalendarNav

Default composed navigation: Today, view switcher, prev/next, title, and a trailing spacer. Pass children to use it as a pure layout shell and compose the nav parts yourself. Renders as a div with useRender support.


EventCalendarNavToday

The "Today" navigation button; resets the calendar to now. It follows the configured navButtonVariant / navButtonSize and the classNames.navButton hook, and gets data-active while the anchor period contains now. EventCalendarNavPrev and EventCalendarNavNext share the same props.


EventCalendarNavPrev

The previous-period navigation button; steps the anchor date one period back for the current view. Same props as EventCalendarNavToday, except tooltip defaults to the i18n "previous" label and children replaces the default chevron icon.


EventCalendarNavNext

The next-period navigation button; steps the anchor date one period forward for the current view. Same props as EventCalendarNavToday, except tooltip defaults to the i18n "next" label and children replaces the default chevron icon.


EventCalendarTitle

The current period title (from i18n.functions.formatTitle), marked aria-live="polite".


EventCalendarViewSwitcher

Dropdown listing the available views (the resolved views option) with optional keyboard-shortcut hints; when the days view is enabled, one item per dayCountPresets entry is offered.


EventCalendarDatePicker

Optional go-to-date popover (shadcn Calendar), not part of the default nav; compose it yourself. It is view-aware: week, N-days, and agenda highlight the whole active range, other views select a single date.


EventCalendarToolbar

Free slot for consumer toolbar buttons; a pure layout shell (flex items-center gap-2) with useRender support and the classNames.toolbar hook. No props beyond standard div props.


EventCalendarContent

Active-view switchboard: renders the component registered for the current view and exposes data-view / data-loading attributes. While loading is true it dims and disables pointer events.


EventCalendarMonthView

ARIA-grid month view: week rows, day cells, continuous multi-day bars in a lane overlay, "+N more" overflow, week numbers, and the day add affordance.


EventCalendarTimeGrid

The shared week/day/N-days engine: hour gutter, minute-positioned event blocks with overlap packing, the all-day row, drag ghosts, and the now indicator. You usually render one of the wrappers below instead.


EventCalendarWeekView

Thin wrapper around EventCalendarTimeGrid with view preset to "week". Accepts every EventCalendarTimeGrid prop except view.


EventCalendarDayView

Thin wrapper around EventCalendarTimeGrid with view preset to "day". Accepts every EventCalendarTimeGrid prop except view.


EventCalendarDaysView

Thin wrapper around EventCalendarTimeGrid with view preset to "days". Accepts every EventCalendarTimeGrid prop except view.


EventCalendarAgendaView

Chronological list grouped by day: sticky date gutters, collapsible days with a color-dot summary row, expandable per-event details (via renderAgendaEventDetails), and a compact empty state. The agenda window length comes from the agendaDayCount option, so the view has no props beyond standard div props.


EventCalendarResourceView

Resource-columns day grid for booking scenarios: one time axis, one column per leaf resource (from the resources option, flattened depth-first), with full drag, resize, and drag-create across columns.


EventCalendarApi

The imperative API, available as instance.api, through apiRef, or from any hook's instance. Reading methods reflect the current snapshot; writing methods respect controlled props (they call the matching on*Change callback and only mutate internal state for uncontrolled fields).


CalendarEvent

Your event objects. TData is a fully generic consumer payload.


EventCalendarResource

A bookable resource (room, person, equipment). Nested resources render their leaves as booking columns in the resource view.


EventCalendarRecurrenceRule

The structured RFC 5545 subset. A raw RRULE string with the same parts is equally accepted on event.recurrence.

EventCalendarWeekday is "MO" \| "TU" \| "WE" \| "TH" \| "FR" \| "SA" \| "SU". For rule parts outside this subset, plug a full RRULE engine through the getOccurrences option; unsupported parts throw EventCalendarRecurrenceError.


EventCalendarOccurrence

One expanded instance of an event, as passed to click callbacks and returned by api.getOccurrences.


EventCalendarSegment

A per-day slice of an occurrence, produced by the event index and consumed by EventCalendarEvent and the layout hooks. Positional fields are filled per surface: startMin / endMin for timed blocks, lane / column / columnCount / columnSpan for packing, and rowIndex / colStart / colSpan for month/all-day bars. isStart / isEnd and continuesBefore / continuesAfter mark cross-day edges.


EventCalendarProposedUpdate

The payload of onEventUpdate and canDropEvent: a proposed timing (and optionally resource) change.

The onEventUpdate return type is EventCalendarUpdateResult: false rejects and reverts, void or true accepts, and { start?, end?, allDay? } accepts with an adjustment.


EventCalendarSlotInfo

The payload of onSlotClick. A click is a point, not a range.


EventCalendarSlotDraft

The drag-create rectangle, passed to onSelectSlot and canSelectSlot. The in-progress draft lives in state as slotDraft and is cleared on commit or cancel; the committed slot selection lives in selection.slot.


EventCalendarState

The store snapshot, as received by useEventCalendarSelector selectors and instance.getState().

EventCalendarDateRange is { start: Date; end: Date } with an inclusive start and an exclusive end. The exported EventCalendarDataAdapter type (getEvents(range, signal?)) describes the shape of an external data source; wiring it to a backend is application territory.

Hooks

Most hooks require an <EventCalendar> ancestor. The exceptions: useEventCalendarState creates the instance itself, useNow is standalone, useEventCalendarSettingsVersion takes the instance as an argument, and hooks that accept an explicit calendar option work outside the tree.

An EventCalendarInstance is { getState, subscribe, api, settings, internals }; internals is cross-file plumbing for sibling view modules and not public API.

Helpers

Pure, React-free helpers exported from event-calendar-lib.tsx (plus the gesture flags from event-calendar-dnd.tsx). event-calendar-lib.tsx also exports the advanced types EventCalendarIndex, EventCalendarDayBucket, and EventCalendarWeekRow used in these signatures, and event-calendar.tsx exports the EventCalendarContext, EventCalendarViewConfigContext, and EventCalendarViewContext context objects for advanced composition.

Useful exported constants: BASE_VIEWS and ALL_VIEWS (the view lists), DEFAULT_VIEW_CONFIG, DEFAULT_VIEW_COMPONENTS (the view switchboard map), EVENT_CALENDAR_ACTIVATION (gesture defaults), EVENT_CALENDAR_COLORS (ten Tailwind palette presets for event.color), EVENT_CALENDAR_GHOST (the standardized drag-ghost classes), EVENT_CALENDAR_FADE_TRUNCATE (the fade-out truncation classes, reusable in renderEvent content), MIN_PACK_SLOT (packing-effective minimum of 30 minutes), and MAX_OCCURRENCES (recurrence expansion cap of 1000 per event).


Recurrence helpers

Exported from event-calendar-recurrence.tsx. The supported RRULE subset is FREQ (daily, weekly, monthly, yearly), INTERVAL, COUNT, UNTIL, BYDAY (with ordinals for monthly/yearly), BYMONTHDAY, BYMONTH, and WKST; anything else throws EventCalendarRecurrenceError, whose message points at the getOccurrences escape hatch.

Config

State options

Option props accepted by EventCalendar and useEventCalendarState. Controlled/uncontrolled pairs follow the standard convention: pass the controlled prop plus its on*Change callback, or the default* prop for internal state.

EventCalendarActivationConfig defaults: moveDistancePx: 5, createDistancePx: 4, touchDelayMs: 250, touchTolerancePx: 5, autoScrollEdgePx: 48, autoScrollMaxStepPx: 15 (exported as EVENT_CALENDAR_ACTIVATION).


Callbacks

Callback props accepted by EventCalendar and useEventCalendarState, alongside the options above.


View configuration

Display props and render overrides, accepted directly on EventCalendar (they live in the view layer, never in the headless options). Defaults come from DEFAULT_VIEW_CONFIG.

Render overrides, all optional and all part of the same view config:

The classNames object (EventCalendarClassNames) offers one string hook per element, cn-merged after the built-in classes so Tailwind variants and ! overrides win. Available keys: nav, toolbar, content, monthView, monthCell, timeGrid, timeGutter, dayColumn, allDaySection, agendaView, event, eventTooltip, moreIndicator, morePopover, morePopoverHeader; nav family navButton, title, navTooltip, viewSwitcherContent, viewSwitcherLabel, viewShortcut, datePickerContent; month view monthHeader, monthDayHeader, monthBody, monthRow, weekNumber, monthBarOverlay, monthBar, monthCellContent, monthCellFooter, monthDayNumber, dayAddButton; time grid and resource timeGridHeader, timeGutterLabel, allDayLabel, allDayCell, timedChip, resourceHeader; interaction surfaces dragGhost, dragCarry, dragCarryInvalid, dropHint, dropIndicator, slotDraft, resizeHandle, resizeGrip; agenda noEvents, agendaDay, agendaDayGutter, agendaDate, agendaDayToggle, agendaDayContent, agendaItem, agendaItemSurface, agendaItemToggle, agendaDaySummary, agendaSummaryDot. Metric CSS variables can ride on any parent key, for example classNames.timeGrid: "[--ec-gutter-width:4rem]" or classNames.morePopover: "[--ec-more-max-height:20rem]".


EventCalendarInteractions

Interaction toggles, controllable via the interactions prop or api.setInteractions. All three default to true.


EventCalendarViewSettings

User-adjustable display toggles (the "view settings" state), controllable via viewSettings / onViewSettingsChange or api.setViewSettings. Every field is optional; undefined defers to the matching view-config prop (see useEventCalendarViewSettings().effective).


EventCalendarOffDaysConfig

Configuration for non-working-day marking, passed as the offDays view config. true uses the defaults (weekends with a muted background); marked cells carry data-off for CSS-selector customization.


Internationalization

The i18n option accepts a Partial<EventCalendarI18nConfig> with four sections, shallow-merged per nested object so a partial override replaces individual keys, never whole sections:

  • labels: UI strings and label functions (today, previous, next, addEvent, allDay, more(count), noEvents, loading, event, events(count), selectView, week(weekNumber), resources, goToDate, dropNotAllowed, continues, timeFrom(time), timeUntil(time), viewShortcuts, toggleDayEvents(count, expanded), eventDetails(title), moreCompact(count), timeRange(from, to)).
  • viewNames: display names per view (month, week, day, days(count), agenda, resource).
  • formats: date-fns format strings applied with the calendar locale (monthTitle, weekTitle, dayTitle, agendaTitle, monthDayHeader, monthDayHeaderNarrow, timeGridDayHeader, agendaDayHeader, agendaDayNumber, agendaWeekday, moreDayHeader, monthCellAriaLabel, dayAria, resourceTitle, timeGutter, timeGutterMinute, eventTime, monthCellDay). Leaving weekTitle / agendaTitle undefined keeps the smart cross-month range title.
  • functions: formatter functions (formatTitle, formatEventTime, formatDayRange, formatEventLabel, formatEventAriaLabel). The defaults are re-bound to the merged labels and formats, so overriding formats.monthTitle flows into the default formatTitle without replacing it.

mergeEventCalendarI18n(overrides?) performs this merge and DEFAULT_EVENT_CALENDAR_I18N holds the defaults; both are exported from event-calendar-i18n.tsx for standalone use.

All 5 variations

Event calendar with resource view for room bookings

Event calendar with create and edit dialog

Event calendar with custom event chips

Event calendar with drag-to-book appointments

Keep in mind

  • MIT License: Free and open-source for personal and commercial use. Feel free to copy, tweak, and adapt the components to match your exact needs.
  • Credits & Inspiration: Some components on this site are inspired by or recreated from great ideas across the web. I'm not here to take credit; just to learn, experiment, and push interface craft further.
  • Reach out & Claims: If an attribution was missed, or if you have any inquiry or claim, feel free to reach out directly on X to @adrielzimbril. I'll be glad to update or resolve it right away.