Customization
Custom Views
Add custom views to the header switcher and sidebar alongside the built-in views.
Custom Views
The customViews prop lets you extend the calendar with fully custom view panels. Each custom view gets a button in the header view switcher (after the built-in views) and a link in the sidebar's "Operations" section.
Interface
interface CustomView {
id: string; // unique — must not collide with built-in view names
label: string; // text shown in button and sidebar
icon?: React.ReactNode; // optional icon for the button/sidebar row
component: React.ReactNode; // rendered when this view is active
}Basic example
import { BarChart2, Users } from 'lucide-react';
const customViews: CustomView[] = [
{
id: 'analytics',
label: 'Analytics',
icon: <BarChart2 size={14} />,
component: (
<div className="p-6">
<h2 className="text-xl font-semibold mb-4">Analytics</h2>
<p className="text-muted-foreground">
Your analytics dashboard goes here.
</p>
</div>
),
},
{
id: 'team',
label: 'Team',
icon: <Users size={14} />,
component: <TeamDashboard />,
},
];
<ProScheduler customViews={customViews} ... />Full example
'use client';
import { useState } from 'react';
import { ProScheduler } from 'calendarkit-prov2';
import type { CalendarEvent, CustomView } from 'calendarkit-prov2';
// Example analytics component
function AnalyticsPanel({ events }: { events: CalendarEvent[] }) {
const byDay = [0, 1, 2, 3, 4, 5, 6].map((d) => ({
day: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'][d],
count: events.filter((e) => new Date(e.start).getDay() === d).length,
}));
return (
<div className="p-6 space-y-6">
<h2 className="text-2xl font-semibold">Analytics</h2>
<p className="text-muted-foreground">Total events: {events.length}</p>
<div className="grid grid-cols-7 gap-2">
{byDay.map(({ day, count }) => (
<div key={day} className="flex flex-col items-center gap-1">
<div
className="w-full bg-blue-500 rounded-sm min-h-[4px] transition-all"
style={{ height: `${Math.max(count * 20, 4)}px` }}
/>
<span className="text-xs text-muted-foreground">{day}</span>
<span className="text-xs font-medium">{count}</span>
</div>
))}
</div>
</div>
);
}
// Example team component
function TeamPanel() {
const members = [
{ name: 'Alice Johnson', role: 'Designer', avatar: '👩🎨' },
{ name: 'Bob Smith', role: 'Developer', avatar: '👨💻' },
{ name: 'Carol White', role: 'Manager', avatar: '👩💼' },
];
return (
<div className="p-6 space-y-4">
<h2 className="text-2xl font-semibold">Team</h2>
{members.map((m) => (
<div key={m.name} className="flex items-center gap-3 p-3 rounded-lg border">
<span className="text-2xl">{m.avatar}</span>
<div>
<p className="font-medium">{m.name}</p>
<p className="text-sm text-muted-foreground">{m.role}</p>
</div>
</div>
))}
</div>
);
}
// SVG icons for the view buttons
const BarChartIcon = () => (
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24"
fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round">
<line x1="18" y1="20" x2="18" y2="10"/>
<line x1="12" y1="20" x2="12" y2="4"/>
<line x1="6" y1="20" x2="6" y2="14"/>
</svg>
);
const TeamIcon = () => (
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24"
fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round">
<path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/>
<circle cx="9" cy="7" r="4"/>
<path d="M23 21v-2a4 4 0 00-3-3.87"/>
<path d="M16 3.13a4 4 0 010 7.75"/>
</svg>
);
export default function CustomViewsExample() {
const [events, setEvents] = useState<CalendarEvent[]>([]);
// Build customViews inside the component so analytics panel gets live events
const customViews: CustomView[] = [
{
id: 'analytics',
label: 'Analytics',
icon: <BarChartIcon />,
component: <AnalyticsPanel events={events} />,
},
{
id: 'team',
label: 'Team',
icon: <TeamIcon />,
component: <TeamPanel />,
},
];
return (
<div className="h-[700px] border rounded-xl overflow-hidden">
<ProScheduler
view="week"
events={events}
date={new Date()}
customViews={customViews}
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>
);
}