Calendar
Scheduling with day, week, month and agenda views, drag-and-drop, recurring events, time zones and Google, Outlook and iCalendar integrations.
KleriCalendar
Drag on the grid to schedule, drag events to move them, pull their bottom edge to resize. Add guests in the editor to see their availability and suggested times. Press ? for shortcuts.
<script lang="ts">
import {
CalendarStore,
KleriCalendar,
createMemoryProvider,
createGoogleCalendarProvider
} from '@kleri/ui/calendar';
const store = new CalendarStore({
providers: [
createMemoryProvider({ calendars: [{ id: 'work', name: 'Work' }] })
],
persistKey: 'my-app-calendar'
});
let view = $state('week');
let date = $state(new Date());
</script>
<div class="h-[720px]">
<KleriCalendar
{store}
bind:view
bind:date
self={{ email: "you@kleri.org", name: "You" }}
{contacts}
{integrations}
/>
</div>Props
Hide Weekends
Week Numbers
Read Only
12-hour Clock
Dim Past Events
Integrations
Every source is a CalendarProvider. Google
Calendar, Microsoft Graph and iCalendar feeds ship built in, with token refresh, retries and
backoff. Pass integrations to power the “Add calendar”
dialog.
import { createGoogleCalendarProvider, createMicrosoftCalendarProvider, createIcsFeedProvider } from '@kleri/ui/calendar';
// Your app owns OAuth; the adapter only needs a token getter.
store.addProvider(
createGoogleCalendarProvider({
account: user.email,
getAccessToken: ({ forceRefresh }) => auth.getGoogleToken({ forceRefresh })
})
);
store.addProvider(
createMicrosoftCalendarProvider({
account: user.email,
getAccessToken: () => msal.acquireTokenSilent({ scopes: ['Calendars.ReadWrite'] }).then((r) => r.accessToken)
})
);
// Read-only iCalendar feeds (Apple, holidays, shared "secret address" URLs…).
// Most feeds lack CORS headers, so route them through your backend.
store.addProvider(
createIcsFeedProvider({
url: 'webcal://example.com/team.ics',
proxy: (url) => `/api/ics?url=${encodeURIComponent(url)}`
})
);
// Anything else: implement the CalendarProvider interface.
const myBackend: CalendarProvider = {
id: 'acme', kind: 'acme', label: 'Acme',
capabilities: { write: true, freeBusy: false, recurrence: 'provider' },
listCalendars: () => api.calendars(),
listEvents: ({ calendarId, start, end, signal }) => api.events(calendarId, start, end, { signal }),
createEvent: (calendarId, draft) => api.create(calendarId, draft)
};Custom views
Views are plain definitions. Timeline or chart views plug in alongside the built-ins and reuse the same data, popovers and scheduling flows through the calendar context.
import { defaultCalendarViews, type CalendarViewDefinition } from '@kleri/ui/calendar';
import TimelineView from './TimelineView.svelte';
const timelineView: CalendarViewDefinition = {
id: 'timeline',
label: 'Timeline',
shortcut: 'l',
component: TimelineView,
range: (date, ctx) => ({ start: startOfWeek(date, ctx.weekStartsOn), end: addDays(startOfWeek(date, ctx.weekStartsOn), 14) }),
step: (date, dir) => addDays(date, 14 * dir),
title: (range, ctx) => formatDateRange(ctx.locale, range.start, addDays(range.end, -1))
};
// <KleriCalendar views={[...defaultCalendarViews, timelineView]} />
// Inside TimelineView.svelte — the calendar context gives you everything:
// const ctx = getCalendarContext();
// ctx.actions.open(occurrence, element) → details popover
// ctx.actions.move(occurrence, start, end) → drag-to-reschedule, with recurring prompts
// ctx.actions.create({ start, end, allDay }) → quick create / scheduling dialog