Configuration
Options and plugins
In vanilla code, pass options inside createCalendar(host, { plugins, options }). In React,
pass options directly to <Calendar> as props. Plugins are selected at creation time. To change
plugins, destroy/recreate the vanilla instance or remount React with a new key.
Set view explicitly to make the starting layout independent of plugin order.
Options resolve from base defaults → plugin defaults → inherited view defaults → your global
options → your per-view overrides. theme, buttonText, icons, and customButtons shallow-merge;
other options replace the previous value. TypeScript's CalendarOptions is the complete input contract.
Setting an option to undefined (or dropping a React prop) restores its default. views is
replaced as a whole: view names, types, and per-view overrides missing from the new map reset.
Toolbars and custom views
Spaces separate button groups; commas join buttons in a group. Use actual view names in the toolbar.
Set headerToolbar={{ start: '', center: '', end: '' }} to hide the built-in toolbar.
firstDay and hiddenDays use Sunday 0 through Saturday 6. Durations accept objects such as
{ days: 3 }, ISO durations such as 'PT30M', time strings such as '00:30', or numbers in seconds.
dateIncrement overrides how far previous/next navigation advances; otherwise the view duration
controls it. Per-view type must name a view provided by a registered plugin or another custom view.
Height and responsive weeks
Use a CSS length string for height, such as '650px', '80vh', or '100%'. Percentage height
needs a defined parent height; in React the host wrapper also needs to fill that parent
(for example, style={{ height: '100%' }}). Without a height, the calendar sizes to its content.
Timed collisions use staggered cards for small clusters and separate strips for dense clusters. Click a day header to widen that date within the week. Hover or keyboard-focus a card for its full details; see overlapping timed events.
Month weeks fill available height. Crowded weeks may grow to display all events. Use
dayMaxEvents: true for uniform rows with a “+N more” popover, or a number to limit visible events.
responsiveWeek defaults to false. true uses a 160px minimum day width and reserves 80px for
the time axis. It fits 1–7 calendar days inside the calendar container, up to the configured
1–7 day duration. Hidden days are still excluded. A positive minDayWidth customizes that threshold.
Shortened ranges start on the selected date; previous/next move by that displayed range unless
dateIncrement overrides it. Full weeks restore firstDay alignment. Resizing preserves the
configured date and duration; getView() reports the actual range. Hidden/zero-width containers
retain their last usable measurement until they become visible.
This behavior applies to timeGridWeek and custom views inheriting it. It does not adapt month,
list, resource time-grid, or timeline views.
Swipe between periods
Enable swipeNavigation: true to move to the previous or next period with a horizontal trackpad
scroll or a one-finger touch swipe. It defaults to false. A Shift+mouse-wheel gesture is also
supported. The demo includes a Swipe to navigate checkbox.
Navigation uses the same increment as the previous/next buttons: day, week, month, a custom
dateIncrement, or the current responsive duration. Right-to-left layouts reverse the gesture
mapping. The calendar body follows the gesture with a rubber-band offset and dims slightly while a
compact chip names the destination period. Crossing the threshold commits the new date and pushes
the periods across as one strip over 360ms: an inert snapshot of the outgoing body slides out while
the new period slides in beside it, and the chip fades as it settles. Releasing early springs back
(180ms). No adjacent calendar views are rendered; the snapshot is the only extra DOM, and it is
removed when the push ends.
A trackpad or Shift+wheel gesture pages at most once. Direction is established from accumulated travel, so tiny axis or reversal jitter is ignored, and the momentum tail that follows a swipe does not page again. A reversal, or a fresh swipe that clearly outgrows the decaying tail, starts a new gesture without waiting for the tail to end; one that lands while the previous period is still leaving runs next. Residual travel is discarded when input stops, so stopping cannot add a navigation step. Touch swipes use distance and velocity on release: a short flick completes and a slow short drag springs back.
When a swipe loads a new range, events from the previous response stay hidden until loading
finishes, then the current results fade in over 160ms. With fetchPadding enabled, adjacent periods
are already cached, so the new period usually arrives with its events in place. An unavailable
destination gives dimmed feedback without navigating. Reduced-motion preferences remove the card and
chip movement: the period swaps as soon as the gesture commits.
Once a gesture is horizontal it is axis-locked: small vertical drift and the momentum tail stay with
the calendar instead of scrolling the page sideways, and page scrolling elsewhere does not cancel
it. Predominantly vertical input hands the gesture back to scrolling. The calendar body sets
touch-action: pan-y pinch-zoom while it has no horizontal overflow of its own, so a horizontal
touch never starts a page pan. Vertical scrolling, zoom gestures,
form controls, popovers, and active event drags retain their normal behavior. Scrollable horizontal
content takes priority: a wide resource grid or timeline continues to scroll within its existing
columns, including at its edges. Use the navigation buttons there, or disable paging per view
through views. Touch paging cancels a pending long press only when horizontal intent is
established; an event drag already in progress keeps control.
Paging respects validRange. Time grids keep their vertical scroll position whenever only the date
changes, by swipe or by the previous/next buttons; scrollTime applies when the view mounts or when
the option or time zone changes, and scrollToFirstEvent repositions each period. The existing buttons remain available for
keyboard navigation. Theme key swipeChip controls the destination chip's class, and the calendar
root carries data-cx-swipe (tracking, settling, or pushing) while a swipe is live, for
styling hooks.
Keyboard navigation and shortcuts
Enable keyboardNavigation: true to move through the calendar with the keyboard. It defaults to
false. One day cell holds the Tab stop: in a month or list view the day, in a time grid the
column of a date, in a timeline the date cell of a resource row. The arrow keys move it, and in
time grids and timelines a slot cursor shows which time is focused. Tab moves from the focused cell
to that date's events and its +N more link, then out of the calendar. The calendar root becomes a
grid and cells gridcells with a full date label (dayCellAriaLabelFormat, default
{ dateStyle: 'full' }), so assistive technology announces where the focus is.
Moving past the first or last visible date pages to the adjacent period and lands on its edge;
PageUp and PageDown keep the date offset. Paging respects validRange like the toolbar buttons.
Right-to-left layouts swap Left and Right. Shift+Arrow needs Interaction and selectable; the
preview runs selectConstraint, Enter fires select with the KeyboardEvent as jsEvent, and a
selection never extends past the visible range. Keyboard dateClick works without Interaction.
The Tab stop follows toolbar, swipe, and API navigation: a focused cell keeps focus under its new
date, and a date that left the view moves to the nearest visible one. calendar.focus(target?) moves
keyboard focus to a date, all-day cell, time (time: '09:30'), or resource from your own controls.
Enable keyboardShortcuts: true for Google Calendar's single keys: t today, j / n next,
k / p previous, and d, w, m, a for the day, week, month, and agenda views that are
registered (each alias tries the time grid, day grid, resource, and list variants in that order).
Pass { keys, target } to replace the map or to listen on document instead of the calendar root:
Actions are today, prev, next, focus (move focus into the grid), { view } with a registered
view name or alias, or a function receiving { key, jsEvent, view }. Shortcuts never fire while a
text field, select, or editable region has focus, while Ctrl, Meta, or Alt is held, or during a
pointer gesture. With the default root target, keys only apply while focus is inside the calendar.
Keep a shortcut object stable across React renders, or rely on the content comparison: equal maps
do not re-attach the listener.
A polite live region announces the new title after every navigation and view change, and the
focused time or pending selection while moving with the keyboard (announceNavigation, default
true). Disable it when your page has its own live title. Theme keys keyboardCursor and
liveRegion name the cursor and region classes, and the root carries data-cx-keyboard while a
cell has focus, for styling hooks.
Scroll to the first event
Set scrollToFirstEvent: true to initially scroll a time-grid or resource time-grid to its earliest
visible timed event. It defaults to false. In a multi-day view, the earliest clock time across
all visible days determines the shared scroll position. Events continuing into the window count;
all-day and background events do not.
The calendar waits for asynchronous event sources to finish. Empty windows use scrollTime
(default 06:00); if their first event arrives later, the calendar scrolls to it. The position is
clamped to the visible slot range, with a little space above the event where possible.
Automatic positioning happens once per displayed range. Scrolling manually or using the calendar's scroll API takes precedence, including while events are loading. Editing or refetching events after the initial scroll does not move you. Navigation or toggling this option starts a fresh initial scroll. The demo includes a Scroll to first event checkbox for trying this behavior.
Fill the time-grid height
Set expandRows: true to stretch time slots across the calendar's available height in time-grid
and resource time-grid views. It defaults to false; existing fixed slot sizing is unchanged.
The calendar needs a constrained height. For height: '100%', give its parent a defined height
and make framework host wrappers fill that parent as described under sizing above. Without a
constrained height, slots retain their natural size.
slotHeight remains the minimum readable height in pixels. Taller containers distribute spare
height across the visible time slots; shorter containers retain the minimum and scroll. Day,
resource, and all-day headers keep their natural sizes. Changes to container size, header content,
or visible time limits recalculate the effective slot height, including after a hidden container
becomes visible.
Event placement, minimum event heights, background events, drag/selection previews, pointer-to-time
mapping, scrolling, and now indicators use the same effective height. getOption('slotHeight')
continues to return the configured minimum. Do not override the internal slot CSS properties to
stretch rows. This option does not change month, list, or horizontal timeline sizing.
Timezone and formatting
timeZone defaults to 'local'. Use an IANA zone such as 'America/New_York', 'UTC', or a fixed
offset. Invalid zones warn and fall back to the local zone; timeZone: undefined restores 'local'.
date defaults to today in the calendar's zone. Explicitly set it for deterministic examples and tests.
locale accepts a locale string or preference array. Date/time format options such as
eventTimeFormat, dayHeaderFormat, titleFormat, and slotLabelFormat accept
Intl.DateTimeFormatOptions or a function receiving Temporal values. Button labels are configured
separately with buttonText; setting a locale does not translate every UI string.
See date semantics before choosing offset-free versus UTC event inputs.
Resources
Resource views need both the relevant plugin and resources. Associate events using resourceId
or resourceIds. IDs normalize to strings. Timeline resources may include nested children and
expanded flags; resourceExpand reports user changes.
resources also accepts a URL object or async/callback source. Function sources always receive the
current fetch info (start, end, startStr, endStr, timeZone). With
refetchResourcesOnNavigate: true, URL sources also get the range as request parameters and every
source reloads on navigation; without it, a source runs once and again only on refetchResources()
or when the source is replaced. getResources, getResourceById, and refetchResources are
available on the instance. Use datesAboveResources to change resource
time-grid header grouping.
Business hours
businessHours shades the time outside working hours and, with the constraint options, keeps edits
inside it. true means Monday to Friday, 09:00 to 17:00. Pass one rule or an array of
{ daysOfWeek, startTime, endTime } rules (Sunday is 0; a window may pass midnight with an end
such as '26:00'), or a function of the date and, in resource views, the resource. Time grids and
timelines shade the closed slots; day grids and the all-day row tint whole closed days. A resource's
own businessHours replaces the calendar's for its column or row.
dragConstraint, resizeConstraint, and selectConstraint accept 'businessHours', inline rules
(the same shapes, without shading), the id of an event the candidate must stay inside, or a
predicate. eventOverlap and selectOverlap decide whether edits and selections may overlap other
events. See constraints and overlap for the rules, and
styling for the --cx-non-business-bg token and the nonBusiness class.