@ascentsparksoftware/react-calendar
v1.0.1
Published
Modern, theme-agnostic calendar & scheduler for React — month/week/day, resource timeline, agenda and year views, timezone-correct, with RRULE recurrence, OKLCH theming and a touch-first interaction layer. SSR/Next.js-safe.
Downloads
263
Maintainers
Readme
@ascentsparksoftware/react-calendar
A modern, theme-agnostic calendar & scheduler for React — month, week, day, year, agenda and a resource timeline — headless-first, SSR/Next.js-safe, timezone-correct, with RFC 5545 recurrence and a touch-first drag/resize engine.
by
✨ Features · 🚀 Quick start · 🎨 Theming · 🧩 API · ♿ Accessibility · 🔢 Versions
A complete scheduling toolkit you drop into a React app as plain components. Render your own
events through the built-in views — month, week/work-week/day, year, agenda, and a
resource timeline (dispatch board) — pass your data as props, and read interactions back as
typed callbacks. Feature-for-feature parity with
@ascentsparksoftware/angular-calendar:
both packages share the same headless core.
Everything is headless-first: all date math, recurrence expansion, overlap-lane layout and
view-model construction are pure functions with no DOM, unit-tested in isolation, and exported
for advanced use. The presentational components render those view models and never compute them
inline. It is timezone-correct from day one (an explicit IANA zone travels through the whole
model — it never leans on the host's local Date), renders caller content as plain text only
(never dangerouslySetInnerHTML; Trusted-Types / strict-CSP clean), and derives its entire
palette from a few color inputs so it drops onto any brand in light or dark with WCAG 2.2 AA
contrast guaranteed.
Features
- Seven views —
month,week,work-week,day(a shared time-grid),year,agenda, and a hierarchical resourcetimelinewith a configurable time axis, collapsible resource groups, working-hours / block-out shading, horizontal or vertical orientation, and row virtualization for long resource lists. - Plain function components + hooks. StrictMode-clean, React-Compiler-safe, no wrapper around your state — props in, callbacks out.
- SSR / Next.js-safe —
"use client"entries, nowindow/documentat module scope, all DOM work inside effects. Drop it behindnext/dynamicor render it directly in a client component. - Timezone-correct — a pluggable date adapter carries an explicit IANA zone through the whole
model. Default adapter is
date-fns+date-fns-tz; a Temporal adapter is a drop-in later. - RFC 5545 recurrence — RRULE series expansion behind an adapter (default
rrule), exceptions, and edit this / this-and-following / all semantics, plus a standalone recurrence editor. - Touch-first interactions — a custom pointer-events layer for drag / create / resize with
snap, long-press, and sub-pixel projection; fully keyboard-operable move/resize. External
drag-in (native HTML drag-and-drop, no extra deps) drops jobs from an outside list onto a
timeline lane. A
validateChangehook vetoes or adjusts any proposed change. - Theme-agnostic —
baseColor+accentColor+themeMode+ astatusColorsmap derive the whole palette as scoped--cal-*CSS variables (OKLCH pipeline), with guaranteed AA contrast. Any single token is host-overridable, and a token bridge defers chosen tokens to your design system's own CSS variables. - Bring your own markup — render props override the event chip, day cell, "+N more" overflow, resource header and the event-detail dialog body, so you keep full control of rendering.
- Accessible — correct ARIA
grid/ roving-tabindex patterns, focus trap + opener restore in the dialog, visible:focus-visiblerings,prefers-reduced-motion, localisable screen-reader strings. - Export — iCalendar (
.ics), CSV (RFC 4180), Excel (SpreadsheetML) and printable HTML, all as pure serializers in a tree-shakable secondary entry point. - Tree-shakable — heavy features (
/date-fns,/recurrence,/export) are secondary entry points; you only pay for what you import.sideEffectslimited to CSS, noany, strict TypeScript.
Install
npm install @ascentsparksoftware/react-calendarPeer dependencies: react and react-dom ^19.
Quick start
import { CalendarProvider, CalMonthView, CalTimeGridView } from '@ascentsparksoftware/react-calendar';
import { DateFnsDateAdapter } from '@ascentsparksoftware/react-calendar/date-fns';
import '@ascentsparksoftware/react-calendar/styles.css';
const adapter = new DateFnsDateAdapter(); // keep the instance stable (module scope or useMemo)
export function Schedule({ events, date, view }) {
return (
<CalendarProvider dateAdapter={adapter} defaults={{ weekStartsOn: 1 }}>
{view === 'month' ? (
<CalMonthView
events={events}
viewDate={date}
eventClicked={({ event }) => console.log(event.id)}
/>
) : (
<CalTimeGridView
events={events}
viewDate={date}
days={view === 'day' ? 1 : 7}
anchorToWeek={view === 'day' ? false : null}
editable
eventChanged={({ change }) => save(change)}
/>
)}
</CalendarProvider>
);
}Events are plain objects — { id, title, start, end, allDay?, status?, resourceId?,
recurrenceRule?, meta? } — where start/end are Dates or { epochMs, zone } pairs.
The calendar never mutates them.
Recurrence
import { RruleRecurrenceAdapter } from '@ascentsparksoftware/react-calendar/recurrence';
<CalendarProvider dateAdapter={adapter} recurrenceAdapter={new RruleRecurrenceAdapter()}>Any event carrying a recurrenceRule (RRULE string) expands into concrete occurrences in
every view, windowed to the visible period.
Next.js
The package ships "use client" entries. In an App Router page, either render the views from
any client component, or load them lazily:
const CalTimelineView = dynamic(
() => import('@ascentsparksoftware/react-calendar').then((m) => m.CalTimelineView),
{ ssr: false },
);Theming
High-level inputs derive the full palette (OKLCH, AA-guaranteed) as --cal-* custom
properties scoped to the component host:
<CalMonthView
events={events}
viewDate={date}
baseColor="#101418"
accentColor="#22c55e"
themeMode="dark"
statusColors={{ confirmed: '#22c55e', pending: '#eab308', cancelled: '#ef4444' }}
/>Each statusColors key yields --cal-event-<key> / -ink / -soft triplets used by event
chips (status: 'confirmed' on the event picks them up). For the long tail, override any
single token with CSS on the host, or bridge tokens to your design system so the calendar
follows your app's theme switching:
<CalendarProvider
dateAdapter={adapter}
tokenBridge={{ '--cal-accent': '--brand-500', '--cal-bg': '--surface', '--cal-ink': '--text' }}
>Bridged tokens win over the derived theme; unbridged tokens keep their derived values. The
theming machinery (deriveTheme, applyTheme, token names) is exported for standalone use.
API
Components
| Component | Purpose |
|---|---|
| CalMonthView | Month grid, multi-day spanning chips, "+N more" overflow popover |
| CalTimeGridView | Week/work-week/day time grid (days 1–7, anchorToWeek, orientation), all-day band, drag/resize/create |
| CalTimelineView | Resource timeline (rows or columns via orientation), grouping, virtualization, drag across resources |
| CalAgendaView | Chronological list, hideEmptyDays |
| CalYearView | 12-month overview with event densities |
| CalEventDialog | Accessible event-detail dialog (focus trap, opener restore, renderEventDetail) |
| CalRecurrenceEditor | RRULE editor (RecurrenceParts in/out) |
| CalTimezonePicker | IANA zone select |
Common props (all views)
events, viewDate, today, timezone, locale, calendarSystem, weekStartsOn,
theming (baseColor, accentColor, themeMode, statusColors, accentInk), and the
callbacks eventClicked, viewPeriodChanged. Time-based views add hour12, slotMinutes,
dayStartMinutes/dayEndMinutes, snapMinutes, editable, validateChange,
eventChanged, slotSelected, externalDrop. Render props: renderEvent, renderCell,
renderOverflow, renderResourceHeader, renderEventDetail.
Provider
<CalendarProvider
defaults={{ locale: 'en-GB', weekStartsOn: 1, slotMinutes: 15, hour12: false }}
dateAdapter={adapter}
recurrenceAdapter={rrule}
tokenBridge={bridge}
virtualization={{ rowThreshold: 60 }}
intl={myIntl} // visible strings
a11y={myA11y} // screen-reader strings
>Headless core
The pure machinery is exported for custom views: buildMonthView, buildTimeGridView,
buildTimelineView, buildAgendaView, buildYearView, expandRecurringEvents,
packColumns, packRows, computeDragTimes, detectConflicts, projection and
virtual-window helpers, plus the full theming API.
Accessibility
ARIA grid/listbox patterns with roving tabindex, every interaction keyboard-operable
(arrow keys move/resize a grabbed event, Enter commits, Escape cancels — with live-region
announcements), focus trap + opener focus restore in the dialog, prefers-reduced-motion
honoured, and AA contrast guaranteed by the theme pipeline in both modes. Screen-reader and
visible strings are both replaceable wholesale for localisation.
Versions
| Package line | React | Status | |---|---|---| | 1.x | ^19.0.0 | Active |
The package major is decoupled from the React major; the peer range is what matters.
Angular consumer? Use
@ascentsparksoftware/angular-calendar
— same core, same tokens, same behavior.
Security
- Caller content is rendered as text nodes only — never
dangerouslySetInnerHTML, no HTML parsing of your data. - Print/export builds documents via
DOMParser(Trusted-Types-safe), not string-injected HTML. - No
eval, noFunction, no dynamic script. - See
SECURITY.mdfor reporting.
License
MIT © Ascentspark Software Private Limited
