Documentation

A lightweight, zero-dependency JavaScript calendar component

Installation

SimpleCalendarJs is a zero-dependency calendar component. Include the CSS and JS files in your project:

Via Script Tags

<link rel="stylesheet" href="simple-calendar-js.css" />
<script src="simple-calendar-js.js"></script>

Via ES Modules

import SimpleCalendarJs from './simple-calendar-js.js';
import './simple-calendar-js.css';

Note: Make sure to include the CSS file for proper styling.

Quick Start

Create a calendar instance by passing a container element and options:

// Create a container in your HTML
<div id="calendar"></div>

// Initialize the calendar
const calendar = new SimpleCalendarJs('#calendar', {
  defaultView: 'month',
  weekStartsOn: 1, // 0 = Sunday, 1 = Monday
  locale: 'en-US',
  use24Hour: false,
  fetchEvents: async (start, end) => {
    // Fetch events from your API
    const response = await fetch(`/api/events?start=${start}&end=${end}`);
    return response.json();
  },
  onEventClick: (event) => {
    console.log('Event clicked:', event);
  },
  onSlotClick: (date) => {
    console.log('Empty slot clicked:', date);
  }
});
View Live Demo

Options

All available options when creating a calendar instance:

View Options

OptionTypeDefaultDescription
defaultViewString'month'Initial view: 'month', 'week', or 'day'
defaultDateDatenullInitial date to display (defaults to today if not provided)
weekStartsOnNumber0First day of week: 0 (Sunday) or 1 (Monday)
localeString'default'Locale code (e.g., 'en-US', 'fr-FR', 'pt-PT'). Supports 34+ locales.
weekdayFormatString'short'Weekday name format: 'narrow' (1-2 letters), 'short' (abbreviated), or 'long' (full name)
use24HourBooleanfalseUse 24-hour time format instead of 12-hour with AM/PM
showTimeInItemsBooleantrueShow time in event items across all views
showGridLinesBooleantrueShow/hide grid lines (borders between cells and time slots)
showBorderBooleantrueShow/hide calendar outer border
maxEventsPerCellNumber3Maximum events per cell in month view before showing "+N more". Set to 0 for unlimited (show all events)

Display Options

Control which toolbar elements are visible:

OptionTypeDefaultDescription
showToolbarBooleantrueShow/hide the entire toolbar
showTodayButtonBooleantrueShow/hide the "Today" button
showNavigationBooleantrueShow/hide navigation arrows (← →)
showTitleBooleantrueShow/hide the month/week/day title
showYearPickerBooleantrueEnable/disable year selection (clickable year)
showViewSwitcherBooleantrueShow/hide Month/Week/Day view switcher buttons
enabledViewsArray['month', 'week', 'day', 'list']Which views are available to switch to
showTooltipsBooleantrueShow/hide event tooltips on hover
enableDragDropBooleanfalseEnable drag and drop to move events
enableResizeBooleanfalseEnable resizing events to change duration
monthTimedEventStyleString'list'Display style for timed events in month view: 'list' (schedule format) or 'block' (traditional blocks)

Example: Start calendar on a specific date:

new SimpleCalendarJs('#calendar', {
  defaultView: 'month',
  defaultDate: new Date(2024, 5, 15) // June 15, 2024
});

Example: Hide grid lines for a cleaner look:

new SimpleCalendarJs('#calendar', {
  showGridLines: false
});

Example: Hide toolbar and only allow month view:

new SimpleCalendarJs('#calendar', {
  showToolbar: false,
  enabledViews: ['month']
});

Example: Customize weekday name format:

// Portuguese with full day names
new SimpleCalendarJs('#calendar', {
  locale: 'pt-PT',
  weekdayFormat: 'long'  // Domingo, Segunda-feira, Terça-feira...
});

// English with narrow format (single letters)
new SimpleCalendarJs('#calendar', {
  locale: 'en-US',
  weekdayFormat: 'narrow'  // S, M, T, W, T, F, S
});

Note: Different locales format the same option differently. Use weekdayFormat to control the style: 'narrow' (1-2 letters), 'short' (abbreviated), or 'long' (full name).

Callbacks

Register event handlers for user interactions:

CallbackParametersDescription
fetchEvents(start: Date, end: Date)Async function to fetch events for the visible date range. Should return an array of event objects.
onEventClick(event: Object)Called when an event is clicked
onSlotClick(date: Date)Called when an empty time slot is clicked
onViewChange(view: String)Called when the view changes (month/week/day)
onNavigate(start: Date, end: Date)Called when navigating to a different date range
onEventDrop(event: Object, oldStart: Date, oldEnd: Date, newStart: Date, newEnd: Date)Called when an event is dropped after being dragged

Event Object Format

Events returned from fetchEvents should have the following structure:

{
  id: 'unique-id',           // Required: Unique identifier
  title: 'Event Title',      // Required: Event name
  start: Date,               // Required: Start date/time
  end: Date,                 // Required: End date/time
  allDay: false,             // Optional: All-day event flag
  color: '#4f46e5',          // Optional: Custom color (hex)
  description: 'Details',    // Optional: Event description (also used for tooltip)
  tooltip: 'Custom tooltip'  // Optional: Custom tooltip text (overrides description)
}

Minute Precision: The calendar supports minute-level precision for event times. Events are positioned exactly according to their start/end times in week and day views. For example, an event starting at 9:15 AM will be positioned 15 minutes (25%) below the 9:00 AM hour line.

Example

const calendar = new SimpleCalendarJs('#calendar', {
  fetchEvents: async (start, end) => {
    return [
      {
        id: '1',
        title: 'Team Meeting',
        start: new Date(2024, 0, 15, 9, 15),  // 9:15 AM
        end: new Date(2024, 0, 15, 10, 45),   // 10:45 AM
        color: '#4f46e5'
      },
      {
        id: '2',
        title: 'Lunch Break',
        start: new Date(2024, 0, 15, 12, 30), // 12:30 PM
        end: new Date(2024, 0, 15, 13, 30),   // 1:30 PM
        allDay: false,
        color: '#059669'
      }
    ];
  },
  onEventClick: (event) => {
    alert(`Clicked: ${event.title}`);
  },
  onSlotClick: (date) => {
    console.log('Create event at:', date);
  }
});

Timezone Handling

SimpleCalendarJs relies on JavaScript's native Date object for timezone handling, which means events are automatically displayed in the user's local timezone.

How It Works

  1. Automatic Conversion: JavaScript Date objects automatically convert to the user's browser timezone
  2. No Configuration Needed: The calendar has no timezone settings - it uses the browser's timezone
  3. Backend Responsibility: Your backend should send timezone-aware date strings

Best Practices

✓ Recommended - Send ISO 8601 strings with timezone

fetchEvents: async (start, end) => {
  const response = await fetch(`/api/events?start=${start}&end=${end}`);
  const events = await response.json();

  // Backend returns ISO 8601 strings with timezone info
  // Example: "2024-03-15T10:00:00Z" (UTC)
  // or: "2024-03-15T10:00:00-05:00" (EST)

  return events.map(event => ({
    ...event,
    start: new Date(event.start), // Automatically converts to local timezone
    end: new Date(event.end)
  }));
}

✗ Avoid - Sending dates without timezone info

// BAD: "2024-03-15T10:00:00" (no timezone)
// JavaScript interprets this as LOCAL time, not UTC
// This can cause issues for users in different timezones

Multi-Timezone Example

Scenario: Your server stores events in UTC, users are in different timezones

// Server returns (stored in UTC):
{
  "title": "Team Meeting",
  "start": "2024-03-15T14:00:00Z",  // 2:00 PM UTC
  "end": "2024-03-15T15:00:00Z"     // 3:00 PM UTC
}

// User in New York (EST, UTC-5):
// Calendar displays: 9:00 AM - 10:00 AM

// User in London (GMT, UTC+0):
// Calendar displays: 2:00 PM - 3:00 PM

// User in Tokyo (JST, UTC+9):
// Calendar displays: 11:00 PM - 12:00 AM

Important Notes

  • Storage: Always store events in UTC in your database
  • API Format: Send dates as ISO 8601 strings with timezone information
  • Display: The calendar automatically shows events in the user's local timezone
  • No Timezone Selector: The calendar doesn't provide UI to change timezone - it always uses the browser's timezone
  • Time Formatting: Uses Intl.DateTimeFormat which respects the user's locale and timezone

Example Backend Response

{
  "events": [
    {
      "id": 1,
      "title": "Global Team Standup",
      "start": "2024-03-15T14:00:00Z",
      "end": "2024-03-15T14:30:00Z",
      "description": "Daily standup - all timezones welcome"
    }
  ]
}

Summary: As long as your backend sends properly formatted ISO 8601 date strings with timezone information (e.g., with 'Z' for UTC or timezone offset like '-05:00'), the calendar will automatically handle timezone conversion for each user's location.

Tooltips

SimpleCalendarJs includes built-in tooltip support for displaying additional event information on hover.

How Tooltips Work

Tooltips appear when you hover over an event for 400ms. They display content based on the following priority:

  1. tooltip property (highest priority) - Custom tooltip text
  2. description property - Falls back if no tooltip is provided
  3. title property - Falls back if neither tooltip nor description is provided

Basic Usage

const events = [
  {
    id: 1,
    title: 'Team Meeting',
    start: new Date(2024, 2, 15, 10, 0),
    end: new Date(2024, 2, 15, 11, 0),
    tooltip: 'Weekly team sync\nDiscuss Q1 roadmap'  // Custom tooltip
  },
  {
    id: 2,
    title: 'Client Call',
    start: new Date(2024, 2, 15, 14, 0),
    end: new Date(2024, 2, 15, 15, 0),
    description: 'Requirements gathering\nPrepare demo'  // Used as tooltip
  },
  {
    id: 3,
    title: 'Code Review',
    start: new Date(2024, 2, 15, 16, 0),
    end: new Date(2024, 2, 15, 17, 0)
    // No tooltip/description - will show title
  }
];

Multiline Tooltips

Use \n (newline character) in your tooltip or description text to create multiple lines:

{
  id: 1,
  title: 'Project Kickoff',
  start: new Date(2024, 2, 20, 9, 0),
  end: new Date(2024, 2, 20, 10, 30),
  tooltip: 'Project Kickoff Meeting\n\nAgenda:\n- Introductions\n- Timeline review\n- Q&A session\n\nLocation: Conference Room A'
}

Tooltip Features

Smart Positioning

Tooltips automatically adjust their position to stay visible:

  • • Events near the top of the viewport: tooltip shows below
  • • Events near the right edge: tooltip aligns to the right
  • • Events near the left edge: tooltip aligns to the left

Visual Design

  • • Dark background with rounded corners
  • • Small arrow pointing to the event
  • • Smooth fade-in animation (400ms delay)
  • • Supports both light and dark themes

Disabling Tooltips

You can disable tooltips globally using the showTooltips option:

const calendar = new SimpleCalendarJs('#calendar', {
  showTooltips: false,  // Disable all tooltips
  fetchEvents: async (start, end) => { /* ... */ }
});

Styling Tooltips

Tooltips use CSS custom properties and can be customized:

:root {
  --cal-tooltip-bg: #1f2937;           /* Background color */
  --cal-tooltip-text: #f9fafb;         /* Text color */
  --cal-tooltip-border: #374151;       /* Border color */
  --cal-tooltip-max-width: 250px;      /* Maximum width */
  --cal-tooltip-padding: 8px 12px;     /* Inner padding */
  --cal-tooltip-radius: 6px;           /* Border radius */
  --cal-tooltip-font-size: 12px;       /* Font size */
  --cal-tooltip-offset: 8px;           /* Distance from event */
}

Limitations: Tooltips display plain text only (no HTML rendering). Special characters are automatically escaped for security. Maximum width is 250px by default (customizable via CSS variables).

Drag and Drop

SimpleCalendarJs supports drag and drop for moving events and resizing them to change their duration.

Configuration

const calendar = new SimpleCalendarJs('#calendar', {
  enableDragDrop: true,  // Enable moving events
  enableResize: true,     // Enable resizing events

  onEventDrop: (event, oldStart, oldEnd, newStart, newEnd) => {
    // Detect if this is a move or resize
    const isMoved = oldStart.getTime() !== newStart.getTime();
    const isResized = oldEnd.getTime() !== newEnd.getTime() && !isMoved;

    if (isMoved) {
      console.log(`Event "${event.title}" moved to ${newStart}`);
    } else if (isResized) {
      console.log(`Event "${event.title}" resized to end at ${newEnd}`);
    }

    // Update your backend
    await fetch(`/api/events/${event.id}`, {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        start: newStart.toISOString(),
        end: newEnd.toISOString(),
        allDay: event.allDay
      })
    });
  }
});

Moving Events (enableDragDrop)

How It Works:

  • Drag Initiation: Click and hold on an event, then move at least 5px or wait 150ms
  • Visual Feedback: The event follows your cursor while dragging
  • Snap to Grid: Events snap to 15-minute intervals in week/day views
  • Drop: Release to drop the event at the new date/time
  • Cancel: Press ESC to cancel the drag operation
  • Duration Preservation: Events maintain their duration when moved
  • Touch Support: Full support for mobile/tablet touch gestures

Cross-Boundary Conversion:

When dragging events between different sections:

Timed Event → All-Day Section (Week/Day Views):

  • • Converts to an all-day event
  • • Preserves the day span

All-Day Event → Timed Section (Week/Day Views):

  • • Converts to a timed event
  • • Default duration: 1 hour
  • • Snaps to the time slot where dropped

Month View:

  • • Events maintain their original type (all-day stays all-day, timed stays timed)
  • • Timed events preserve their original time of day

Resizing Events (enableResize)

Horizontal Resize (All-Day Events):

  • Visual Indicator: Small vertical line appears on the right edge when hovering
  • How It Works: Drag the right edge to change the number of days the event spans
  • Available In: Month view, week/day all-day sections
  • Minimum: 1 day

Vertical Resize (Timed Events):

  • Visual Indicator: Small horizontal line appears at the bottom when hovering
  • How It Works: Drag the bottom edge to change the end time
  • Available In: Week/day timed sections
  • Snap to Grid: 15-minute intervals
  • Minimum: 15 minutes
  • Live Feedback: Time display updates to show start and end times as you drag

Callback Parameters

The onEventDrop callback receives the same parameters for both move and resize operations:

ParameterTypeDescription
eventObjectThe updated event object (with new start/end)
oldStartDateOriginal start date/time
oldEndDateOriginal end date/time
newStartDateNew start date/time
newEndDateNew end date/time

Detecting Operation Type:

  • Move: oldStart !== newStart
  • Resize: oldStart === newStart and oldEnd !== newEnd

Important Notes:

  • • The calendar updates the event internally before firing the callback
  • • You must update your backend in the onEventDrop callback
  • • If the backend update fails, call calendar.refresh() to revert to the previous state
  • • Both features are disabled in list view (read-only)
  • • You can enable one, both, or neither feature independently

Month View Timed Event Display Style

The monthTimedEventStyle option controls how timed events are displayed in month view:

List Style (Default: 'list')

Schedule-style display with horizontal layout:

  • Colored dot: Shows event color as a small circle
  • Time: Displays start time (if showTimeInItems is enabled)
  • Title: Event title truncated with ellipsis if too long
  • Compact: Clean, minimal appearance similar to schedule apps
const calendar = new SimpleCalendarJs('#calendar', {
  monthTimedEventStyle: 'list',  // Default
  showTimeInItems: true           // Shows time next to dot
});

Block Style ('block')

Traditional calendar block display:

  • Colored Background: Full event background in event color
  • Time Display: Start time shown inside block (if enabled)
  • Classic Look: Traditional calendar appearance
const calendar = new SimpleCalendarJs('#calendar', {
  monthTimedEventStyle: 'block'
});

Note: This option only affects timed events in month view. All-day events always display as blocks, and week/day views always use block style with duration-based heights.

List View

Display upcoming events in a chronological list format. Perfect for mobile devices and quick overview of upcoming events.

Basic Usage

const calendar = new SimpleCalendarJs('#calendar', {
  defaultView: 'list',              // Start in list view
  enabledViews: ['month', 'week', 'day', 'list'], // Include list in views
  listDaysForward: 30              // Show next 30 days (default)
});

Features

  • • Shows events from current date/time forward
  • • Groups events by date with sticky headers
  • • Color-coded event indicators
  • • Mobile-friendly design
  • • Configurable range with listDaysForward option

Behavior

Upcoming Events View

List view always displays events from the current date/time forward, unlike other views which are navigable:

  • No navigation buttons: List view doesn't have previous/next navigation
  • No "Today" button: Already showing upcoming events from now
  • Date range title: Shows the range being displayed (e.g., "Feb 22 - Mar 24, 2026")
  • Multi-day events: Active multi-day events appear under today's date, not their start date

Configuration Example

// Show next 90 days in list view
new SimpleCalendarJs('#calendar', {
  defaultView: 'list',
  enabledViews: ['month', 'list'],  // Only month and list views
  listDaysForward: 90,
  fetchEvents: async (start, end) => {
    // Your event fetching logic
  }
});

Note: List view is opt-in. Add 'list' to the enabledViews array to enable it.

Methods

Programmatically control the calendar:

MethodParametersDescription
setView(view)'month' | 'week' | 'day' | 'list'Switch to a different view
navigate(direction)numberNavigate by offset: 1 = next, -1 = previous
goToDate(date)DateJump to a specific date
goToToday()-Jump to today's date
refresh()-Clear event cache and re-fetch events
destroy()-Remove calendar and cleanup event listeners

Example

const calendar = new SimpleCalendarJs('#calendar', { /* options */ });

// Switch to week view
calendar.setView('week');

// Navigate to next week
calendar.navigate(1);

// Jump to a specific date
calendar.goToDate(new Date(2024, 5, 15));

// Go to today
calendar.goToToday();

// Refresh calendar (clear cache and re-fetch)
calendar.refresh();

// Cleanup when done
calendar.destroy();

Event Caching

SimpleCalendarJs intelligently caches fetched events to minimize unnecessary network requests:

  • Smart fetch strategy: Always fetches the full month grid (including leading/trailing days), regardless of current view
  • Automatic caching: Events are cached by date range after each fetch
  • Efficient reuse: View switches and navigation within the cached range use cached data (no spinner, instant display)
  • Fetch only when needed: New data is fetched only when the required range extends beyond the cache
  • Manual refresh: Call calendar.refresh() when events change externally (added, updated, or deleted)

How it works:

  • • When in month/week/day views: Fetches the full month grid for the current date context
  • • When in list view: Fetches the specific forward range (configurable with listDaysForward)
  • • Subsequent navigation within the same month uses cached data
  • • Navigation to a different month triggers a new fetch for that month's full grid
// Month view Feb 2026: Fetches Jan 28 - Mar 1 (full month grid)
calendar.setView('month');  // ✓ Fetch: Jan 28 - Mar 1

// Switch to week view (Feb 20-26): Already in cache, no fetch
calendar.setView('week');   // ✗ No fetch (instant)

// Switch to day view (Feb 22): Already in cache, no fetch
calendar.setView('day');    // ✗ No fetch (instant)

// Navigate next week (Feb 27 - Mar 5): Extends beyond cache
// Fetches March's full month grid (Mar 1 - Apr 5)
calendar.navigate(1);       // ✓ Fetch: Mar 1 - Apr 5

// Navigate next week (Mar 6-12): Already in March's cache
calendar.navigate(1);       // ✗ No fetch (instant)

// Navigate previous week (Feb 27 - Mar 5): Partially outside cache
// Fetches February's full month grid (Jan 28 - Mar 1)
calendar.navigate(-1);      // ✓ Fetch: Jan 28 - Mar 1

// After external event changes, refresh to clear cache
addEventToDatabase(newEvent);
calendar.refresh();         // ✓ Fetch (clears cache)

Persisting User Preferences

The calendar doesn't include built-in persistence - this is intentionally left to your application so you have full control over how and where preferences are stored.

Using LocalStorage (Persistent Across Sessions)

// Restore saved preferences or use defaults
const savedView = localStorage.getItem('calendarView') || 'month';
const savedDate = localStorage.getItem('calendarDate');

const calendar = new SimpleCalendarJs('#calendar', {
  defaultView: savedView,
  defaultDate: savedDate ? new Date(savedDate) : null,

  // Save view changes
  onViewChange: (view) => {
    localStorage.setItem('calendarView', view);
  },

  // Save navigation (current date)
  onNavigate: (startDate) => {
    localStorage.setItem('calendarDate', startDate.toISOString());
  }
});

Using SessionStorage (Per-Tab)

// Same as localStorage but survives only for the current tab/session
const calendar = new SimpleCalendarJs('#calendar', {
  defaultView: sessionStorage.getItem('calendarView') || 'month',
  onViewChange: (view) => sessionStorage.setItem('calendarView', view),
  onNavigate: (start) => sessionStorage.setItem('calendarDate', start.toISOString())
});

Using URL Query Parameters (Shareable State)

// Read from URL
const params = new URLSearchParams(window.location.search);
const view = params.get('view') || 'month';
const date = params.get('date') ? new Date(params.get('date')) : null;

const calendar = new SimpleCalendarJs('#calendar', {
  defaultView: view,
  defaultDate: date,

  onViewChange: (newView) => {
    const url = new URL(window.location);
    url.searchParams.set('view', newView);
    window.history.pushState({}, '', url);
  },

  onNavigate: (startDate) => {
    const url = new URL(window.location);
    url.searchParams.set('date', startDate.toISOString().split('T')[0]);
    window.history.pushState({}, '', url);
  }
});

React Example with State Hook

function MyCalendar() {
  const [view, setView] = useState(
    () => localStorage.getItem('calendarView') || 'month'
  );

  const [currentDate, setCurrentDate] = useState(
    () => {
      const saved = localStorage.getItem('calendarDate');
      return saved ? new Date(saved) : new Date();
    }
  );

  const handleViewChange = (newView) => {
    setView(newView);
    localStorage.setItem('calendarView', newView);
  };

  const handleNavigate = (startDate) => {
    setCurrentDate(startDate);
    localStorage.setItem('calendarDate', startDate.toISOString());
  };

  return (
    <SimpleCalendarJsReact
      defaultView={view}
      defaultDate={currentDate}
      onViewChange={handleViewChange}
      onNavigate={handleNavigate}
      fetchEvents={fetchEvents}
    />
  );
}

Vue Example with Composable

// composables/useCalendarPreferences.js
import { ref, watch } from 'vue';

export function useCalendarPreferences() {
  const view = ref(localStorage.getItem('calendarView') || 'month');
  const currentDate = ref(
    localStorage.getItem('calendarDate')
      ? new Date(localStorage.getItem('calendarDate'))
      : new Date()
  );

  watch(view, (newView) => {
    localStorage.setItem('calendarView', newView);
  });

  watch(currentDate, (newDate) => {
    localStorage.setItem('calendarDate', newDate.toISOString());
  });

  return { view, currentDate };
}

// In component
<script setup>
import { useCalendarPreferences } from './composables/useCalendarPreferences';
const { view, currentDate } = useCalendarPreferences();

const handleViewChange = (newView) => {
  view.value = newView;
};

const handleNavigate = (startDate) => {
  currentDate.value = startDate;
};
</script>

<template>
  <SimpleCalendarJsVue
    :defaultView="view"
    :defaultDate="currentDate"
    @viewChange="handleViewChange"
    @navigate="handleNavigate"
  />
</template>

Server-Side Preferences (Synced Across Devices)

// Load preferences from your API
const preferences = await fetch('/api/user/calendar-preferences').then(r => r.json());

const calendar = new SimpleCalendarJs('#calendar', {
  defaultView: preferences.view || 'month',
  defaultDate: preferences.lastViewedDate ? new Date(preferences.lastViewedDate) : null,

  onViewChange: async (view) => {
    // Debounce to avoid too many API calls
    clearTimeout(window.savePreferencesTimeout);
    window.savePreferencesTimeout = setTimeout(() => {
      fetch('/api/user/calendar-preferences', {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ view })
      });
    }, 1000);
  }
});

What to Persist

Common preferences to save:

  • View mode: 'month', 'week', 'day', or 'list'
  • Current date: Last viewed date for returning to the same position
  • Enabled views: Which view modes the user prefers to have available
  • UI preferences: Dark mode, show/hide options

Note: The calendar is designed to be stateless - all state management is your responsibility, giving you full flexibility over storage method, persistence duration, and privacy controls.

Theming

SimpleCalendarJs supports dark mode and color customization via CSS custom properties.

Dark Mode

Toggle dark mode by adding the uc-dark class to the calendar container:

// Enable dark mode
calendar._root.classList.add('uc-dark');

// Disable dark mode
calendar._root.classList.remove('uc-dark');

// Toggle dark mode
calendar._root.classList.toggle('uc-dark', isDark);

Override Light Mode Colors

Target the .uc-calendar class to customize light mode:

.uc-calendar {
  /* Primary color */
  --cal-primary: #10b981;           /* Green theme */
  --cal-primary-dark: #059669;
  --cal-primary-light: #d1fae5;

  /* Today indicator */
  --cal-today-bg: #d1fae5;
  --cal-today-text: #059669;
}

Override Dark Mode Colors

Target .uc-calendar.uc-dark to customize dark mode:

.uc-calendar.uc-dark {
  /* Dark mode colors */
  --cal-bg: #1f2937;
  --cal-bg-secondary: #111827;
  --cal-text: #f9fafb;
  --cal-border: #374151;

  /* Dark mode today/selected indicators */
  --cal-today-bg: #065f46;          /* Custom dark green */
  --cal-selected-bg: #047857;
}

Customize Both Light and Dark Modes

Example with a complete brand color scheme:

/* Light mode - Yellow theme */
.uc-calendar {
  --cal-primary: #eab308;           /* yellow-500 */
  --cal-primary-dark: #ca8a04;      /* yellow-600 */
  --cal-primary-light: #fef9c3;     /* yellow-100 */
  --cal-today-bg: #fef9c3;
  --cal-today-text: #854d0e;
}

/* Dark mode - Yellow theme */
.uc-calendar.uc-dark {
  --cal-primary: #eab308;
  --cal-primary-dark: #facc15;
  --cal-primary-light: #713f12;
  --cal-today-bg: #713f12;          /* yellow-900 */
  --cal-selected-bg: #854d0e;       /* yellow-800 */
}

Available CSS Variables

VariableCategoryDescription
--cal-primaryColorsPrimary theme color
--cal-primary-darkColorsDarker primary (hover states)
--cal-primary-lightColorsLighter primary (backgrounds)
--cal-bgColorsBackground color
--cal-bg-secondaryColorsSecondary background
--cal-textColorsPrimary text color
--cal-text-subtleColorsSubtle text color
--cal-borderColorsBorder color
--cal-today-bgColorsToday indicator background
--cal-selected-bgColorsSelected date background
--cal-font-sizeSizingBase font size (default: 13px)
--cal-hour-heightSizingHour row height (default: 60px)
--cal-event-heightSizingEvent bar height (default: 22px)
--cal-tooltip-*TooltipsTooltip colors, sizing, typography
--cal-loading-bgLoadingLoading spinner overlay background (default: rgba(255, 255, 255, 0.7) in light mode, rgba(31, 41, 55, 0.7) in dark mode)

Tip: Use .uc-calendar.uc-dark to override dark mode colors separately from light mode. This allows you to create cohesive themes that work in both modes.

React

Use the React wrapper for seamless integration with React applications.

Installation

import SimpleCalendarJsReact from './simple-calendar-js-react.jsx';
import './simple-calendar-js.css';

Basic Usage

import { useState } from 'react';
import SimpleCalendarJsReact from './simple-calendar-js-react.jsx';

function App() {
  const [isDark, setIsDark] = useState(false);

  const fetchEvents = async (start, end) => {
    const response = await fetch(`/api/events?start=${start}&end=${end}`);
    return response.json();
  };

  const handleEventClick = (event) => {
    console.log('Event clicked:', event);
  };

  return (
    <>
      <button onClick={() => setIsDark(!isDark)}>
        Toggle Theme
      </button>
      <SimpleCalendarJsReact
        defaultView="month"
        defaultDate={new Date(2024, 5, 15)}
        weekStartsOn={1}
        darkMode={isDark}
        fetchEvents={fetchEvents}
        onEventClick={handleEventClick}
        onSlotClick={(date) => console.log('Slot clicked:', date)}
        onViewChange={(view) => console.log('View changed:', view)}
        style={{ height: '600px' }}
      />
    </>
  );
}

Using Imperative Methods

import { useRef } from 'react';

function App() {
  const calRef = useRef();

  const handleNextWeek = () => {
    calRef.current.setView('week');
    calRef.current.navigate(1);
  };

  return (
    <>
      <button onClick={handleNextWeek}>Next Week</button>
      <SimpleCalendarJsReact ref={calRef} />
    </>
  );
}

Props

  • • All calendar options are available as props
  • darkMode - Boolean prop for theme control
  • onEventClick, onSlotClick, onViewChange, onNavigate - Event handlers
  • style, className - Apply to wrapper div

Vue

Use the Vue 3 wrapper with Composition API.

Installation

import SimpleCalendarJsVue from './simple-calendar-js-vue.js';
import './simple-calendar-js.css';

Basic Usage

<script setup>
import { ref } from 'vue';
import SimpleCalendarJsVue from './simple-calendar-js-vue.js';

const isDark = ref(false);

const fetchEvents = async (start, end) => {
  const response = await fetch(`/api/events?start=${start}&end=${end}`);
  return response.json();
};

const handleEventClick = (event) => {
  console.log('Event clicked:', event);
};
</script>

<template>
  <button @click="isDark = !isDark">Toggle Theme</button>
  <SimpleCalendarJsVue
    :defaultView="'month'"
    :defaultDate="new Date(2024, 5, 15)"
    :weekStartsOn="1"
    :darkMode="isDark"
    :fetchEvents="fetchEvents"
    @eventClick="handleEventClick"
    @slotClick="(date) => console.log('Slot clicked:', date)"
    @viewChange="(view) => console.log('View changed:', view)"
    :style="{ height: '600px' }"
  />
</template>

Using Imperative Methods

<script setup>
import { ref } from 'vue';

const calendarRef = ref();

const handleNextWeek = () => {
  calendarRef.value.setView('week');
  calendarRef.value.navigate(1);
};
</script>

<template>
  <button @click="handleNextWeek">Next Week</button>
  <SimpleCalendarJsVue ref="calendarRef" />
</template>

Props & Events

  • • All calendar options are available as props (use kebab-case or camelCase)
  • :darkMode - Boolean prop for theme control
  • @eventClick, @slotClick, @viewChange, @navigate - Vue events
  • :customClass - Apply CSS class to wrapper

Angular

Use the Angular standalone component (or NgModule).

Installation

import { SimpleCalendarJsComponent } from './simple-calendar-js-angular.component';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [SimpleCalendarJsComponent],
  // ...
})

Basic Usage

import { Component } from '@angular/core';
import { SimpleCalendarJsComponent } from './simple-calendar-js-angular.component';

@Component({
  selector: 'app-calendar-demo',
  standalone: true,
  imports: [SimpleCalendarJsComponent],
  template: `
    <button (click)="isDark = !isDark">Toggle Theme</button>
    <simple-calendar-js
      [defaultView]="'month'"
      [defaultDate]="new Date(2024, 5, 15)"
      [weekStartsOn]="1"
      [darkMode]="isDark"
      [fetchEvents]="fetchEvents"
      (eventClick)="handleEventClick($event)"
      (slotClick)="handleSlotClick($event)"
      (viewChange)="handleViewChange($event)"
      [customStyle]="{ height: '600px' }"
    ></simple-calendar-js>
  `
})
export class CalendarDemoComponent {
  isDark = false;

  fetchEvents = async (start: Date, end: Date) => {
    const response = await fetch(`/api/events?start=${start}&end=${end}`);
    return response.json();
  };

  handleEventClick(event: any) {
    console.log('Event clicked:', event);
  }

  handleSlotClick(date: Date) {
    console.log('Slot clicked:', date);
  }

  handleViewChange(view: string) {
    console.log('View changed:', view);
  }
}

Using Methods via ViewChild

import { Component, ViewChild } from '@angular/core';
import { SimpleCalendarJsComponent } from './simple-calendar-js-angular.component';

@Component({
  // ...
})
export class CalendarDemoComponent {
  @ViewChild(SimpleCalendarJsComponent) calendar!: SimpleCalendarJsComponent;

  handleNextWeek() {
    this.calendar.setView('week');
    this.calendar.navigate(1);
  }
}

Inputs & Outputs

  • • All calendar options are available as @Input properties
  • [darkMode] - Boolean input for theme control
  • (eventClick), (slotClick), (viewChange), (navigate) - Angular outputs
  • [customClass], [customStyle] - Apply to wrapper div

Custom Styling

Customize the calendar appearance with CSS.

Example: Green Theme

.my-green-calendar {
  --cal-primary: #10b981;
  --cal-primary-dark: #059669;
  --cal-primary-light: #d1fae5;
}

Example: Larger Text

.my-calendar {
  --cal-font-size: 14px;
  --cal-event-height: 26px;
}

Example: Custom Event Styling

/* Target all events */
.uc-calendar .uc-event {
  border-radius: 6px;
  font-weight: 600;
}

/* Target timed events in week/day view */
.uc-calendar .uc-timed-event {
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

Important CSS Classes

ClassTarget
.uc-calendarCalendar root container
.uc-darkDark mode modifier
.uc-toolbarTop toolbar
.uc-eventEvent elements
.uc-timed-eventTimed events in week/day view
.uc-todayToday's date cell