New: CalSync Server — self-hosted calendar infrastructure with multi-tenancy, WhatsApp & email notificationsLearn more
Documentation

CalSync Documentation

Everything you need — from embedding the React calendar component to deploying your own self-hosted server.

Installation

Install CalSync into your React project. A valid license key is required to get the installation token for the private npm registry.

Step 1: Get Your Install Token

Use the license key from your email to get the npm install token. This token is needed to authenticate with the GitHub Packages registry.

Get Install Token

Step 2: Configure .npmrc

Create a .npmrc file in your project root with the token you received:

.npmrc
@calsync-react:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=YOUR_INSTALL_TOKEN

Step 3: Install the package

bash
npm install @calsync-react/calendar

Or with other package managers:

bash
pnpm add @calsync-react/calendar
bash
yarn add @calsync-react/calendar

Step 4: Import styles

Import the CalSync stylesheet in your app entry point or layout:

tsx
import "@calsync-react/calendar/styles.css";

Important: Add .npmrc to your .gitignore to avoid committing your token to version control.

Quick Start

The simplest way to use CalSync — drop it in and it works out of the box. In uncontrolled mode, CalSync manages events and agendas internally using localStorage.

tsx
import { CalendarApp } from "@calsync-react/calendar";
import "@calsync-react/calendar/styles.css";

function App() {
  return (
    <div className="h-screen w-screen">
      <CalendarApp
        locale="en"
        preventOverlap={true}
        defaultView="month"
        licenseKey="YOUR_LICENSE_KEY"
      />
    </div>
  );
}

Tip: In uncontrolled mode, events and agendas persist in the browser's localStorage automatically. No backend needed to get started.

Calendar Views

CalSync offers four built-in views. Users can switch between them using the toolbar tabs, or you can set the initial view via the defaultView prop.

month

Month View

Full month grid with event dots. Click a day to drill into it. Events are shown as colored badges.

week

Week View

7-day column layout with hourly time slots. Events are rendered as resizable, draggable blocks.

day

Day View

Single day with full time grid. Ideal for detailed scheduling and fine-grained event placement.

year

Year View

12-month overview showing event density per day. Great for spotting patterns across the whole year.

tsx
// Set initial view
<CalendarApp defaultView="week" />

// Set initial view + initial date
<CalendarApp
  defaultView="day"
  defaultDate={new Date(2025, 5, 15)} // June 15, 2025
/>

Tip: Use onViewChange to track which view the user selected and persist it across sessions (e.g. in localStorage or URL params).

Event Management

CalSync supports full CRUD for events: create via modal, edit inline or via modal, drag to reschedule, resize to change duration, and delete.

Creating Events

Click the + button or click an empty time slot in week/day view. A modal opens with fields for description, event type, date, time, location, and agenda.

tsx
<CalendarApp
  onEventCreate={(event) => {
    console.log("New event:", event);
    // event has: id, title, description, start, end, type, location, agendaId
    await api.events.create(event);
  }}
/>

Editing Events

Click any event to open the edit modal. Changes are emitted via onEventUpdate.

tsx
<CalendarApp
  onEventUpdate={(event) => {
    // Fires on: modal save, drag-move, resize
    await api.events.update(event.id, event);
  }}
  onEventClick={(event) => {
    // Fires before the edit modal opens
    analytics.track("event_clicked", { id: event.id });
  }}
/>

Deleting Events

tsx
<CalendarApp
  onEventDelete={(eventId) => {
    await api.events.delete(eventId);
  }}
/>

Agendas

Agendas act as color-coded categories for events (e.g. per team, per project, per department). Each event belongs to exactly one agenda. Users can toggle agendas on/off to filter the calendar.

Uncontrolled (default)

By default, agendas are stored in localStorage. Users create and delete them via the sidebar.

tsx
// CalSync manages agendas internally
<CalendarApp locale="en" />

Controlled Agendas

Pass the agendas prop to manage them yourself. CalSync will not use localStorage for agendas when this prop is provided.

tsx
const [agendas, setAgendas] = useState<Agenda[]>([
  {
    id: "1",
    name: "Team Alpha",
    color: "#3b82f6",
    isActive: true,
    eventCount: 0,
    createdAt: new Date(),
  },
  {
    id: "2",
    name: "Team Beta",
    color: "#10b981",
    isActive: true,
    eventCount: 0,
    createdAt: new Date(),
  },
]);

<CalendarApp
  agendas={agendas}
  onAgendaCreate={(agenda) => {
    setAgendas((prev) => [...prev, agenda]);
  }}
  onAgendaDelete={(agendaId) => {
    setAgendas((prev) => prev.filter((a) => a.id !== agendaId));
  }}
/>

Note: When an agenda is deleted, all events linked to it are also removed. The onAgendaDelete callback fires after the user confirms the deletion in the built-in confirmation dialog.

Drag & Drop

CalSync uses @dnd-kit for smooth drag-and-drop interactions. Both week and day views support:

Drag to reschedule

Grab an event and move it to a new time slot or day. The onEventUpdate callback fires with the new start/end times.

Resize to change duration

Drag the bottom edge of an event to shorten or extend it. Works in both week and day views.

Overlap prevention

When preventOverlap is true (default), CalSync blocks drops that would cause two events to overlap on the same agenda.

tsx
<CalendarApp
  preventOverlap={true}   // block overlapping events (default)
  onEventUpdate={(event) => {
    // Fires after drag-move or resize completes
    console.log(`Event ${event.id} moved to ${event.start}`);
  }}
/>

// Allow overlapping events
<CalendarApp preventOverlap={false} />

Availability Rules

Define color-coded availability blocks that render behind events in week and day views. When availability rules are set, events can only be created within those time slots. Overlapping rules on the same day are automatically prevented.

Visual availability blocks

Colored overlays render behind the time grid in week and day views, making it easy to see when scheduling is allowed.

Event creation enforcement

When rules exist, drag-to-create and modal-based event creation are blocked outside availability windows.

Overlap prevention

Adding a new rule that overlaps an existing one on the same day(s) is automatically rejected.

Sidebar management

Users can add and remove availability rules from the sidebar. Each rule has a label, days, time range, and color.

Uncontrolled (default)

By default, users manage availability rules via the sidebar UI. Use the onAvailabilityChange callback to sync changes to your backend.

tsx
<CalendarApp
  onAvailabilityChange={(rules) => {
    console.log("Updated rules:", rules);
    // Save to your backend
    await api.availability.save(rules);
  }}
/>

Controlled Availability

Pass the availabilityRules prop to manage rules externally.

tsx
import type { AvailabilityRule } from "@calsync-react/calendar";

const [rules, setRules] = useState<AvailabilityRule[]>([
  {
    id: "office-hours",
    label: "Office Hours",
    days: [1, 2, 3, 4, 5],   // Mon–Fri
    startHour: 9,
    endHour: 17,
    color: "bg-green-300",
  },
  {
    id: "saturday-morning",
    label: "Saturday Morning",
    days: [6],                // Saturday
    startHour: 10,
    endHour: 13,
    color: "bg-blue-300",
  },
]);

<CalendarApp
  availabilityRules={rules}
  onAvailabilityChange={(updated) => setRules(updated)}
/>

Tip: If no availability rules are set, event creation is unrestricted. Once at least one rule exists, events can only be created within those time windows.

Recurring Events

CalSync supports recurring events with flexible frequency options. When creating or editing an event, users can enable recurrence and configure the pattern directly in the modal.

daily

Daily

Repeats every N days. Example: every day, every 2 days.

weekly

Weekly

Repeats on specific days of the week. Example: every Monday and Wednesday.

monthly

Monthly

Repeats on the same day of the month. Example: the 15th of every month.

yearly

Yearly

Repeats on the same date each year. Example: January 1st every year.

Recurrence End Conditions

Each recurring event can end in one of three ways:

Never

The event repeats indefinitely.

After N occurrences

The event stops after a set number of repetitions (e.g. after 10 occurrences).

Until a date

The event repeats until a specific end date.

Programmatic Recurrence

When creating events programmatically in controlled mode, attach a recurrence object to the event:

tsx
import type { CalendarEvent, RecurrenceRule } from "@calsync-react/calendar";

const weeklyMeeting: CalendarEvent = {
  id: "weekly-standup",
  title: "Team Standup",
  description: "Daily sync",
  start: new Date(2025, 5, 16, 9, 0),
  end: new Date(2025, 5, 16, 9, 30),
  type: "Meeting",
  recurrence: {
    frequency: "weekly",
    interval: 1,
    byDay: [1, 3, 5],  // Mon, Wed, Fri
    count: 52,          // 1 year of weekly meetings
  },
};

Note: Recurring events are rendered as badges with a frequency indicator in the agenda view. The recurrence pattern is fully editable from the event edit modal.

Timezone Support

CalSync supports IANA timezone strings. By default, it uses the browser's local timezone. Pass the timezone prop to override it.

tsx
// Use browser timezone (default)
<CalendarApp />

// Force a specific timezone
<CalendarApp timezone="America/New_York" />
<CalendarApp timezone="Europe/London" />
<CalendarApp timezone="Asia/Tokyo" />

When a timezone is set, all events created will be tagged with that timezone internally. This is useful for applications where users operate across multiple time zones.

Tip: The timezone value is stored on each event object as event.timezone, so you can use it when syncing with external calendar systems (Google Calendar, Outlook, etc.).

Controlled Mode

For full control, pass events and/or agendas props. When provided, CalSync will not use localStorage for those items — you manage all state yourself.

tsx
import { useState } from "react";
import { CalendarApp } from "@calsync-react/calendar";
import type { CalendarEvent, ViewType } from "@calsync-react/calendar";

function App() {
  const [events, setEvents] = useState<CalendarEvent[]>([]);
  const [view, setView] = useState<ViewType>("month");

  return (
    <CalendarApp
      events={events}
      defaultView={view}
      licenseKey="YOUR_LICENSE_KEY"
      onEventCreate={(e) => {
        setEvents((prev) => [...prev, e]);
        // save to your DB here
      }}
      onEventUpdate={(e) => {
        setEvents((prev) => prev.map((x) => (x.id === e.id ? e : x)));
      }}
      onEventDelete={(id) => {
        setEvents((prev) => prev.filter((x) => x.id !== id));
      }}
      onViewChange={(v) => setView(v)}
    />
  );
}

Note: You can control events and agendas independently. For example, pass events to control events while letting CalSync manage agendas internally, or vice versa.

Callbacks

CalSync exposes granular callbacks for every user interaction. Use them to sync with your backend, analytics, or external state.

CallbackWhen it fires
onEventCreateUser creates a new event via the modal
onEventUpdateEvent is edited, dragged, or resized
onEventDeleteUser deletes an event
onEventClickUser clicks an event (before edit modal)
onAgendaCreateUser creates a new agenda
onAgendaDeleteUser confirms agenda deletion
onAvailabilityChangeAvailability rules are added or removed
onViewChangeUser switches between month/week/day/year
onDateChangeUser navigates to a different date

Full example with all callbacks

tsx
<CalendarApp
  licenseKey="CALSYNC-XXXX-XXXX-XXXX"
  locale="en"
  defaultView="week"
  // Event callbacks
  onEventCreate={(event) => api.events.create(event)}
  onEventUpdate={(event) => api.events.update(event.id, event)}
  onEventDelete={(id) => api.events.delete(id)}
  onEventClick={(event) => console.log("Clicked:", event.title)}
  // Agenda callbacks
  onAgendaCreate={(agenda) => api.agendas.create(agenda)}
  onAgendaDelete={(id) => api.agendas.delete(id)}
  // Navigation callbacks
  onViewChange={(view) => router.push(`?view=${view}`)}
  onDateChange={(date) => router.push(`?date=${date.toISOString()}`)}
/>

Settings

Configure the visible time range and initial state of the calendar.

Visible Time Range

In week and day views, use defaultSettings to limit the visible hours. By default, all 24 hours are shown (0–23).

tsx
// Show only business hours (8 AM – 6 PM)
<CalendarApp
  defaultSettings={{ startHour: 8, endHour: 18 }}
  defaultView="week"
/>

// Morning clinic (6 AM – 1 PM)
<CalendarApp
  defaultSettings={{ startHour: 6, endHour: 13 }}
  defaultView="day"
/>

Environment Variable for License

Avoid hardcoding your license key. Use an environment variable instead.

bash
# .env.local
NEXT_PUBLIC_CALSYNC_KEY=CALSYNC-XXXX-XXXX-XXXX
tsx
<CalendarApp
  licenseKey={process.env.NEXT_PUBLIC_CALSYNC_KEY}
/>

Full Configuration Example

tsx
import { CalendarApp } from "@calsync-react/calendar";
import type { CalendarEvent, Agenda, ViewType } from "@calsync-react/calendar";

function MyCalendar() {
  const [events, setEvents] = useState<CalendarEvent[]>([]);
  const [agendas, setAgendas] = useState<Agenda[]>([]);
  const [view, setView] = useState<ViewType>("week");
  const [date, setDate] = useState(new Date());

  return (
    <CalendarApp
      // License
      licenseKey={process.env.NEXT_PUBLIC_CALSYNC_KEY}
      // Appearance
      locale="en"
      defaultView={view}
      defaultDate={date}
      defaultSettings={{ startHour: 7, endHour: 20 }}
      // Behaviour
      preventOverlap={true}
      // Controlled data
      events={events}
      agendas={agendas}
      // Event callbacks
      onEventCreate={(e) => setEvents((p) => [...p, e])}
      onEventUpdate={(e) => setEvents((p) => p.map((x) => x.id === e.id ? e : x))}
      onEventDelete={(id) => setEvents((p) => p.filter((x) => x.id !== id))}
      // Agenda callbacks
      onAgendaCreate={(a) => setAgendas((p) => [...p, a])}
      onAgendaDelete={(id) => setAgendas((p) => p.filter((x) => x.id !== id))}
      // Navigation callbacks
      onViewChange={setView}
      onDateChange={setDate}
    />
  );
}

Props API

Complete reference for all CalendarApp props.

PropTypeDefaultDescription
eventsCalendarEvent[]undefinedControlled events list. When provided, localStorage is NOT used for events.
agendasAgenda[]undefinedControlled agendas list. When provided, localStorage is NOT used for agendas.
defaultView"month" | "week" | "day" | "year""month"Initial calendar view to display.
defaultDateDatetodayInitial date the calendar is centered on.
defaultSettings{ startHour: number; endHour: number }{ startHour: 0, endHour: 23 }Visible time range for week and day views.
localeLocale"fr"Language for the UI. See Localization section.
preventOverlapbooleantruePrevent overlapping events on drag, resize, and create.
licenseKeystringundefinedLicense key validated against the CalSync server.
customFieldsCustomField[][]Extra fields rendered in the event create/edit modals. Values stored in event.customData.
availabilityRulesAvailabilityRule[]undefinedControlled availability rules. Color-coded blocks shown in week/day views.
onAvailabilityChange(rules: AvailabilityRule[]) => voidFires when availability rules are added or removed.
timezonestringbrowser TZIANA timezone string (e.g. "America/New_York"). Defaults to the browser timezone.
onEventCreate(event: CalendarEvent) => voidFires when a new event is created via the modal.
onEventUpdate(event: CalendarEvent) => voidFires when an event is moved, resized, or edited.
onEventDelete(eventId: string) => voidFires when an event is deleted.
onEventClick(event: CalendarEvent) => voidFires when an event is clicked.
onAgendaCreate(agenda: Agenda) => voidFires when a new agenda is created.
onAgendaDelete(agendaId: string) => voidFires when an agenda is deleted.
onViewChange(view: ViewType) => voidFires when the user switches calendar view.
onDateChange(date: Date) => voidFires when the user navigates to a different date.

Types

TypeScript types exported from CalSync for use in your application.

CalendarEvent

tsx
type CalendarEvent = {
  id: string;
  title: string;
  description: string;
  start: Date;
  end: Date;
  type: string;
  location?: string;
  agendaId?: string;
  customData?: Record<string, string | number | boolean>;
  recurrence?: RecurrenceRule;
  timezone?: string;
};

AvailabilityRule

tsx
type AvailabilityRuleDay = 0 | 1 | 2 | 3 | 4 | 5 | 6; // Sun=0 … Sat=6

type AvailabilityRule = {
  id: string;
  label: string;
  days: AvailabilityRuleDay[];
  startHour: number;   // 0–23 (decimals for half-hours, e.g. 9.5 = 9:30)
  endHour: number;
  color: string;       // Tailwind bg class, e.g. "bg-green-300"
};

RecurrenceRule

tsx
type RecurrenceFrequency = "daily" | "weekly" | "monthly" | "yearly";

type RecurrenceRule = {
  frequency: RecurrenceFrequency;
  interval?: number;        // Every N days/weeks/months/years (default 1)
  byDay?: number[];         // For weekly: days of week (0=Sun … 6=Sat)
  count?: number;           // Stop after N occurrences
  until?: string;           // Stop on this date (ISO string)
};

CustomField

Define extra fields for the event create/edit modals via the customFields prop. Values are stored in event.customData.

tsx
type CustomFieldType = "text" | "number" | "select" | "textarea" | "checkbox" | "date" | "time";

interface CustomField {
  name: string;        // Unique key used in customData
  label: string;       // Display label
  type: CustomFieldType;
  required?: boolean;
  placeholder?: string;
  defaultValue?: string | number | boolean;
  options?: { label: string; value: string }[];  // For "select" type
}

Custom Fields Example

tsx
<CalendarApp
  licenseKey="YOUR_KEY"
  customFields={[
    { name: "patient", label: "Patient Name", type: "text", required: true, placeholder: "Enter patient name" },
    { name: "room", label: "Room", type: "select", options: [
      { label: "Room A", value: "a" },
      { label: "Room B", value: "b" },
    ]},
    { name: "notes", label: "Notes", type: "textarea", placeholder: "Additional notes..." },
    { name: "urgent", label: "Urgent", type: "checkbox" },
  ]}
  onEventCreate={(event) => {
    console.log(event.customData);
    // { patient: "John Doe", room: "a", notes: "...", urgent: true }
  }}
/>

Agenda

tsx
type Agenda = {
  id: string;
  name: string;
  color: string;
  isActive: boolean;
  eventCount: number;
  createdAt: Date;
};

ViewType

tsx
type ViewType = "month" | "week" | "day" | "year" | "agenda";

Localization

CalSync ships with built-in translations for 10 languages. Set the locale prop to switch languages.

en
English
fr
French
es
Spanish
de
German
it
Italian
pt
Portuguese
nl
Dutch
ja
Japanese
zh
Chinese
ko
Korean
tsx
<CalendarApp locale="en" />  // English
<CalendarApp locale="fr" />  // French (default)
<CalendarApp locale="ja" />  // Japanese

License Key

CalSync requires a valid license key to unlock all features. Without a key, the component runs in a restricted demo mode.

Free Trial (7 days)

  • • Full access to all features for 7 days
  • • Up to 3 events during the trial period
  • • License key sent to your email instantly
  • • No credit card required

Pro License (€499, one-time)

  • • Unlimited events and agendas
  • • Lifetime updates and bug fixes
  • • Priority email support
  • • Commercial use license

Using your license key

tsx
<CalendarApp
  licenseKey="CALSYNC-XXXX-XXXX-XXXX-XXXX"
  locale="en"
  defaultView="month"
/>

How it works: On mount, the component sends your license key to the CalSync validation server. It returns the license status (valid, trial, expired) and enforces feature limits accordingly.

The validation happens once on component mount and is cached for the session.