A Svelte 5 component library wrapping FullCalendar with shadcn-svelte styling. Fully themeable, accessible, and designed to integrate seamlessly with your existing shadcn design system.
$state, $derived, and $effect for optimal reactivitynpm install @agsci/fullcalendar-svelte-shadcn @fullcalendar/core
Install the FullCalendar plugins you need:
# For day/week views (time grid)
npm install @fullcalendar/daygrid @fullcalendar/timegrid
# For drag-and-drop and selection
npm install @fullcalendar/interaction
# For list view
npm install @fullcalendar/list
<script lang="ts">
import { Calendar, CalendarNav, type CalendarEvent, type CalendarView } from '@agsci/fullcalendar-svelte-shadcn';
import '@agsci/fullcalendar-svelte-shadcn/styles';
let events = $state<CalendarEvent[]>([
{
id: '1',
title: 'Team Meeting',
start: new Date(),
end: new Date(Date.now() + 60 * 60 * 1000),
backgroundColor: '#3b82f6'
}
]);
let viewedDate = $state(new Date());
let currentView = $state<CalendarView>('week');
let calendarRef: { prev: () => void; next: () => void; today: () => void; changeView: (v: CalendarView) => void };
</script>
<CalendarNav
{viewedDate}
{currentView}
onPrev={() => calendarRef?.prev()}
onNext={() => calendarRef?.next()}
onToday={() => calendarRef?.today()}
onViewChange={(v) => { currentView = v; calendarRef?.changeView(v); }}
/>
<Calendar
bind:this={calendarRef}
{events}
config={{ initialView: 'week', editable: true, selectable: true }}
onEventClick={(event) => console.log('Clicked:', event)}
onDatesSet={(start) => viewedDate = start}
onViewChange={(v) => currentView = v}
/>
The main calendar component wrapping FullCalendar.
<Calendar
events={events}
config={{
initialView: 'week', // 'day' | 'week' | 'month' | 'list'
slotMinTime: '07:00', // Earliest time slot
slotMaxTime: '21:00', // Latest time slot
height: '600px', // Calendar height
editable: true, // Enable drag-and-drop
selectable: true, // Enable date range selection
nowIndicator: true, // Show current time line
firstDay: 0 // Week start (0=Sunday, 1=Monday)
}}
onEventClick={(event) => {}}
onEventChange={(event, oldEvent, revert) => {}}
onDateSelect={(start, end, allDay) => {}}
onDateClick={(date, allDay) => {}}
onDatesSet={(start, end) => {}}
onViewChange={(view) => {}}
/>
Navigation controls with month/year selectors and view switcher.
<CalendarNav
viewedDate={viewedDate}
currentView={currentView}
onPrev={() => calendarRef?.prev()}
onNext={() => calendarRef?.next()}
onToday={() => calendarRef?.today()}
onViewChange={(view) => calendarRef?.changeView(view)}
onDateChange={(date) => calendarRef?.gotoDate(date)}
/>
Pre-built dialogs for viewing, adding, editing, and deleting events.
<script>
import { EventView, EventAddDialog, EventEditDialog, EventDeleteDialog } from '@agsci/fullcalendar-svelte-shadcn';
</script>
<EventView
event={selectedEvent}
open={viewOpen}
onClose={() => viewOpen = false}
onEdit={(event) => { /* open edit dialog */ }}
onDelete={(event) => { /* open delete dialog */ }}
/>
<EventAddDialog
open={addOpen}
defaultStart={selectedStart}
defaultEnd={selectedEnd}
onClose={() => addOpen = false}
onAdd={(eventData) => events = [...events, { id: Date.now().toString(), ...eventData }]}
/>
<EventEditDialog
event={selectedEvent}
open={editOpen}
onClose={() => editOpen = false}
onSave={(id, updates) => events = events.map(e => e.id === id ? { ...e, ...updates } : e)}
/>
<EventDeleteDialog
event={selectedEvent}
open={deleteOpen}
onClose={() => deleteOpen = false}
onConfirm={(id) => events = events.filter(e => e.id !== id)}
/>
The calendar automatically inherits your shadcn theme. It uses CSS custom properties that map to your theme tokens:
/* The library uses these variables (with sensible fallbacks) */
:root {
--fc-border-color: var(--border);
--fc-bg: var(--background);
--fc-text: var(--foreground);
--fc-muted: var(--muted-foreground);
--fc-today-bg: var(--accent);
--fc-hover-bg: var(--secondary);
--fc-primary: var(--primary);
--fc-primary-foreground: var(--primary-foreground);
}
No extra configuration needed - if you have shadcn-svelte set up, the calendar matches your theme automatically.
Override specific calendar styles in your app's CSS:
/* Make the calendar more compact */
:root {
--fc-slot-height: 2.5rem;
--fc-header-height: 2.5rem;
}
/* Custom event colors */
.fc-event {
--fc-event-radius: 0.5rem;
}
/* Different today highlight */
.fc-day-today {
--fc-today-bg: hsl(var(--primary) / 0.1);
}
The calendar respects your dark mode setup. If you use the standard .dark class approach:
.dark {
--border: hsl(240 3.7% 15.9%);
--background: hsl(240 10% 3.9%);
--foreground: hsl(0 0% 98%);
/* ... your other dark mode tokens */
}
The calendar will automatically adapt.
Pass custom classes to components:
<Calendar class="rounded-lg shadow-lg" />
<CalendarNav class="mb-4 px-2" />
Control event appearance via the event data:
const event: CalendarEvent = {
id: '1',
title: 'Important Meeting',
start: new Date(),
end: new Date(),
backgroundColor: '#ef4444', // Red background
textColor: '#ffffff', // White text
};
Or use the color picker in the Add/Edit dialogs.
interface CalendarEvent {
id: string;
title: string;
start: Date;
end: Date;
description?: string;
backgroundColor?: string;
textColor?: string;
allDay?: boolean;
extendedProps?: Record<string, unknown>;
}
interface CalendarConfig {
initialView?: 'day' | 'week' | 'month' | 'list';
slotMinTime?: string; // e.g., '07:00'
slotMaxTime?: string; // e.g., '21:00'
firstDay?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
editable?: boolean;
selectable?: boolean;
nowIndicator?: boolean;
height?: string | number;
allDaySlot?: boolean;
timeZone?: string;
}
type CalendarView = 'day' | 'week' | 'month' | 'list';
For complex apps, use the provided state utilities:
import { createCalendarState, setCalendarContext, getCalendarContext } from '@agsci/fullcalendar-svelte-shadcn';
// In a parent component
const calendarState = createCalendarState(initialEvents);
setCalendarContext(calendarState);
// In child components
const state = getCalendarContext();
state.addEvent(newEvent);
state.selectEvent(event);
state.openModal('add');
Helpful date utilities are exported:
import {
formatDate, // 'Dec 8, 2024'
formatTime, // '2:30 PM'
formatDateRange, // 'Dec 8, 2:00 PM - 3:00 PM'
isSameDay,
isToday,
toDateString, // '2024-12-08' (for inputs)
toTimeString, // '14:30' (for inputs)
} from '@agsci/fullcalendar-svelte-shadcn';
# Install dependencies
pnpm install
# Start dev server
pnpm dev
# Type check
pnpm check
# Build library
pnpm build
MIT