Calendarkit Pro V2
Customization

Render Props

Replace the header, event modal, mini-calendar, and empty state with your own components.

Render Props

CalendarKit Pro exposes four render-prop overrides that let you replace key UI regions with your own components while keeping all the calendar logic intact.

renderEventForm

Replace the default event creation/edit modal:

renderEventForm?: (props: {
  isOpen:       boolean;
  onClose:      () => void;
  event?:       CalendarEvent | null;  // null = creating new
  initialDate?: Date;
  onSave:       (event: Partial<CalendarEvent>) => void;
  onDelete?:    (eventId: string) => void;
}) => React.ReactNode

Example: custom event drawer

import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet';

<ProScheduler
  renderEventForm={({ isOpen, onClose, event, initialDate, onSave, onDelete }) => (
    <Sheet open={isOpen} onOpenChange={(open) => !open && onClose()}>
      <SheetContent>
        <SheetHeader>
          <SheetTitle>{event ? 'Edit Event' : 'New Event'}</SheetTitle>
        </SheetHeader>

        <MyCustomForm
          event={event}
          initialDate={initialDate}
          onSubmit={(data) => { onSave(data); onClose(); }}
          onDelete={event ? () => { onDelete?.(event.id); onClose(); } : undefined}
          onCancel={onClose}
        />
      </SheetContent>
    </Sheet>
  )}
  ...
/>

renderHeader

Replace the entire header bar:

renderHeader?: (props: {
  currentDate:      Date;
  view:             ViewType;
  onPrev:           () => void;
  onNext:           () => void;
  onToday:          () => void;
  onViewChange:     (view: ViewType) => void;
  translations:     CalendarTranslations;
  language:         LanguageCode;
  onLanguageChange?: (lang: LanguageCode) => void;
  isDarkMode?:      boolean;
  onThemeToggle?:   () => void;
}) => React.ReactNode

Example: minimal custom header

import { ChevronLeft, ChevronRight } from 'lucide-react';
import { format } from 'date-fns';

<ProScheduler
  renderHeader={({ currentDate, onPrev, onNext, onToday, view, onViewChange, translations }) => (
    <div className="flex items-center justify-between px-4 py-3 border-b">
      {/* Navigation */}
      <div className="flex items-center gap-2">
        <button onClick={onPrev} className="p-1.5 rounded hover:bg-muted">
          <ChevronLeft size={16} />
        </button>
        <span className="font-semibold text-sm min-w-[160px] text-center">
          {format(currentDate, 'MMMM yyyy')}
        </span>
        <button onClick={onNext} className="p-1.5 rounded hover:bg-muted">
          <ChevronRight size={16} />
        </button>
        <button
          onClick={onToday}
          className="px-3 py-1 text-xs border rounded-lg hover:bg-muted ml-2"
        >
          {translations.today}
        </button>
      </div>

      {/* View switcher */}
      <div className="flex gap-1">
        {(['month', 'week', 'day', 'agenda'] as const).map((v) => (
          <button
            key={v}
            onClick={() => onViewChange(v)}
            className={`px-3 py-1 text-xs rounded-lg transition-colors ${
              view === v
                ? 'bg-primary text-primary-foreground'
                : 'hover:bg-muted'
            }`}
          >
            {translations[v]}
          </button>
        ))}
      </div>
    </div>
  )}
  ...
/>

renderMiniCalendar

Replace the mini calendar in the sidebar:

renderMiniCalendar?: (props: {
  currentDate:  Date;
  onDateChange: (date: Date) => void;
  onViewChange?: (view: ViewType) => void;
}) => React.ReactNode

Example: use a third-party date picker

import DatePicker from 'react-datepicker';
import 'react-datepicker/dist/react-datepicker.css';

<ProScheduler
  renderMiniCalendar={({ currentDate, onDateChange }) => (
    <div className="p-3">
      <DatePicker
        selected={currentDate}
        onChange={(date) => date && onDateChange(date)}
        inline
      />
    </div>
  )}
  ...
/>

renderEmptyState

Replace the Agenda view's empty state:

renderEmptyState?: (props: {
  view:           ViewType;
  onCreateEvent?: () => void;
}) => React.ReactNode

Example: custom empty state

<ProScheduler
  renderEmptyState={({ onCreateEvent }) => (
    <div className="flex flex-col items-center justify-center h-full gap-6 py-20 text-center">
      <div className="text-6xl">📅</div>
      <div>
        <h3 className="text-lg font-semibold mb-2">No upcoming events</h3>
        <p className="text-muted-foreground text-sm max-w-xs">
          Your schedule is clear. Start by creating a new event.
        </p>
      </div>
      <button
        onClick={onCreateEvent}
        className="px-5 py-2.5 bg-primary text-primary-foreground rounded-lg text-sm font-medium hover:opacity-90"
      >
        + Create Event
      </button>
    </div>
  )}
  ...
/>

Full example using all render props

'use client';

import { useState } from 'react';
import { ProScheduler } from 'calendarkit-prov2';
import type { CalendarEvent, ViewType } from 'calendarkit-prov2';
import { format } from 'date-fns';

export default function RenderPropsExample() {
  const [events, setEvents] = useState<CalendarEvent[]>([]);
  const [view,   setView]   = useState<ViewType>('week');
  const [date,   setDate]   = useState(new Date());
  const [modal,  setModal]  = useState(false);
  const [editing, setEditing] = useState<CalendarEvent | null>(null);
  const [initDate, setInitDate] = useState<Date | undefined>();

  return (
    <div className="h-[700px] border rounded-xl overflow-hidden">
      <ProScheduler
        view={view}
        onViewChange={setView}
        events={events}
        date={date}
        onDateChange={setDate}

        // Custom header — minimal
        renderHeader={({ currentDate, onPrev, onNext, onToday, view: v, onViewChange: ovc, translations }) => (
          <div className="flex items-center justify-between px-4 py-3 border-b bg-background">
            <div className="flex items-center gap-2">
              <button onClick={onPrev}  className="p-1.5 rounded hover:bg-muted text-sm">←</button>
              <span className="font-bold text-sm">{format(currentDate, 'MMMM yyyy')}</span>
              <button onClick={onNext}  className="p-1.5 rounded hover:bg-muted text-sm">→</button>
              <button onClick={onToday} className="px-2 py-1 text-xs border rounded ml-1 hover:bg-muted">
                {translations.today}
              </button>
            </div>
            <div className="flex gap-1">
              {(['month', 'week', 'day', 'agenda'] as ViewType[]).map((vv) => (
                <button
                  key={vv}
                  onClick={() => ovc(vv)}
                  className={`px-3 py-1 text-xs rounded capitalize ${
                    v === vv ? 'bg-primary text-primary-foreground' : 'hover:bg-muted'
                  }`}
                >
                  {vv}
                </button>
              ))}
            </div>
          </div>
        )}

        // Custom event form — simple inline approach
        renderEventForm={({ isOpen, onClose, event, initialDate, onSave, onDelete }) => {
          if (!isOpen) return null;
          return (
            <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
              <div className="bg-background rounded-xl shadow-2xl w-full max-w-md p-6 space-y-4">
                <h2 className="text-lg font-semibold">{event ? 'Edit Event' : 'New Event'}</h2>
                <input
                  id="custom-title"
                  defaultValue={event?.title ?? ''}
                  placeholder="Event title"
                  className="w-full px-3 py-2 border rounded-lg text-sm"
                />
                <div className="flex justify-between">
                  <button onClick={onClose} className="px-4 py-2 text-sm border rounded-lg hover:bg-muted">
                    Cancel
                  </button>
                  <div className="flex gap-2">
                    {event && onDelete && (
                      <button
                        onClick={() => { onDelete(event.id); onClose(); }}
                        className="px-4 py-2 text-sm bg-red-500 text-white rounded-lg hover:bg-red-600"
                      >
                        Delete
                      </button>
                    )}
                    <button
                      onClick={() => {
                        const title = (document.getElementById('custom-title') as HTMLInputElement).value;
                        onSave({ ...event, title, start: event?.start ?? initialDate ?? new Date(), end: event?.end ?? new Date() });
                        onClose();
                      }}
                      className="px-4 py-2 text-sm bg-primary text-primary-foreground rounded-lg"
                    >
                      Save
                    </button>
                  </div>
                </div>
              </div>
            </div>
          );
        }}

        // Custom empty state
        renderEmptyState={({ onCreateEvent }) => (
          <div className="flex flex-col items-center justify-center h-full gap-4 text-center py-16">
            <span className="text-5xl">🗓️</span>
            <p className="text-muted-foreground">No events yet</p>
            <button onClick={onCreateEvent} className="px-4 py-2 text-sm bg-primary text-primary-foreground rounded-lg">
              + Add Event
            </button>
          </div>
        )}

        onEventCreate={(e) =>
          setEvents((prev) => [...prev, { ...e, id: crypto.randomUUID() } as CalendarEvent])
        }
        onEventUpdate={(updated) =>
          setEvents((prev) => prev.map((e) => (e.id === updated.id ? updated : e)))
        }
        onEventDelete={(id) =>
          setEvents((prev) => prev.filter((e) => e.id !== id))
        }
      />
    </div>
  );
}

On this page