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.ReactNodeExample: 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.ReactNodeExample: 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.ReactNodeExample: 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.ReactNodeExample: 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>
);
}