About
A feature-rich calendar for SvelteKit, built with Svelte 5 runes, Tailwind v4, and shadcn-svelte. Day, week, month, and agenda views with drag-and-drop events. A Svelte port of the popular lramos33/big-calendar.
Documentation
Big Calendar โ Svelte
A feature-rich calendar built with SvelteKit, Svelte 5 runes, TypeScript, and Tailwind CSS v4 โ five views, full event CRUD, drag-and-drop, per-user filtering, configurable working hours, and event locations with online-meeting links and an embedded map.

Big Calendar bundles five views, full event CRUD, drag-and-drop scheduling, undo/redo, a command palette, and optional per-event locations into one SvelteKit app. Everything runs in the browser โ events persist to localStorage, seeded from sample data on a first visit, with no backend to set up.
Features
- ๐
Five calendar views
- Agenda, Year, Month, Week (detailed time slots), Day (hourly breakdown)
- โ๏ธ Create, edit, and delete events
- Drag across empty grid slots in the week or day view to block out a new event
- Your events are saved in this browser (
localStorage), and survive a reload - "Reset to sample data" in settings puts the demo back the way it started
- โฉ๏ธ Undo and redo
โZ/Ctrl+Zundoes,โงโZ/Ctrl+Yredoes โ every create, edit, move, resize, and delete- Each change raises a toast with an Undo button
- โจ๏ธ Command palette and keyboard shortcuts
โK/Ctrl+Kto switch views, jump to today, create an event, or search events by titlettoday,nnew event,gthend/w/m/y/ato switch view,โ/โto page,?for the full list
- ๐ Event locations
- Attach an optional location to any event: an online meeting or a physical address
- Online links show the platform's favicon and a one-click Join button โ the platform (Zoom, Google Meet, Teams, Jitsi, โฆ) is detected from the URL, so any link works
- Physical addresses render on an embedded map, geocoded from the address string
- See Event locations
- ๐จ Event customization
- Seven event colors
- Three badge variants (dot, colored, mixed)
- Single- and multi-day events, with multi-day events spanning cells
- Busy month cells reveal their hidden events in a popover
- ๐ฑ๏ธ Drag and drop
- Drag events onto another day in the month view, or onto any quarter-hour slot in the week and day views
- Drag across empty slots to create an event over exactly that range;
Escabandons the drag - Resize events by their top or bottom edge to change start or end time, with keyboard support
- ๐ฅ User management
- Filter events by user, or view everyone at once
- Avatar group in the user picker
- โก Real-time
- Live current-time indicator on the week and day grids
- "Happening now" panel in the day view
- โฐ Time customization
- Per-weekday working hours, with non-working hours hatched
- Adjustable visible-hours range that auto-expands to fit outlying events
- ๐ฏ UI/UX
- Responsive, accessible, keyboard-navigable
- Light and dark themes, toggled from the header and remembered across reloads
- Loading skeletons until events hydrate, and empty states that offer a Create button
Screenshots
The five views
| Month โ the busy-cell overflow grid | Week โ quarter-hour time slots |
![]() | ![]() |
| Day โ hourly breakdown | Year โ twelve months at a glance |
![]() | ![]() |
Agenda โ a chronological list grouped by day

Locations
An event can carry an optional location. Online links resolve the platform from the URL and offer a one-click Join; physical addresses render on an embedded Leaflet map, geocoded from the address string.
| Online meeting โ favicon + Join | Physical address โ embedded map |
![]() | ![]() |
Creating, editing, and settings
| Add event โ validated form | Edit event โ location pre-expanded |
![]() | ![]() |
| Location picker โ online / in person | Calendar settings โ badge style, visible & working hours |
![]() | ![]() |
Command palette, shortcuts, and filtering
Command palette (โK / Ctrl+K) | Keyboard shortcuts (?) |
![]() | ![]() |
Filter by user โ an avatar group in the picker, or pick one person

Dark mode
Every view supports a light and a dark theme, toggled from the header and remembered across reloads.
Show dark-theme views
![]() | ![]() |
![]() | ![]() |

Tech stack
| Choice | |
|---|---|
| Framework | SvelteKit 2 / Svelte 5 (runes) |
| Language | TypeScript |
| Styling | Tailwind CSS v4 |
| UI components | shadcn-svelte (bits-ui) |
| Forms | sveltekit-superforms + formsnap + zod 4 |
| Date handling | date-fns v4 |
| Maps | Leaflet + OpenStreetMap |
| Icons | @lucide/svelte |
| State | Svelte 5 runes + context |
| Package manager | bun |
Getting started
git clone https://github.com/DevRohit06/big-calendar-svelte.git
cd big-calendar-svelte
bun install
bun run dev
Open http://localhost:5173. The root route redirects to /month-view.
Other scripts:
bun run check # svelte-check + tsc
bun run lint # prettier --check && eslint
bun run format # prettier --write
bun run build # production build
Usage
1. Provide the calendar state
Wrap your page or layout in CalendarProvider. It creates the shared state on context and, in a client-only effect, seeds events from localStorage (or requests.ts on a first visit):
<!-- +layout.svelte -->
<script lang="ts">
import CalendarProvider from '$lib/calendar/contexts/calendar-provider.svelte';
let { children } = $props();
</script>
<CalendarProvider>
{@render children()}
</CalendarProvider>
Wiring the calendar into an existing app or a real backend? See the integration guide.
2. Render a view
<script lang="ts">
import ClientContainer from '$lib/calendar/components/client-container.svelte';
</script>
<ClientContainer view="month" />
view accepts "day" | "week" | "month" | "year" | "agenda".
3. Read and control state anywhere
The calendar exposes its state as a class instance from context:
<script lang="ts">
import { getCalendarState } from '$lib/calendar/contexts/calendar-context.svelte';
const calendar = getCalendarState();
// Read properties off `calendar` directly โ destructuring loses reactivity.
function jumpToToday() {
calendar.selectDate(new Date());
}
</script>
<p>Showing {calendar.selectedDate.toDateString()}</p>
CalendarState exposes the reactive fields selectedDate, selectedUserId, badgeVariant, visibleHours, workingHours, users, events, and hydrated, plus the methods selectDate(), addEvent(), updateEvent(), deleteEvent(), undo(), redo(), and reset(). See the integration guide to route these through a backend.
Data structures
interface IEvent {
id: number;
title: string;
description: string;
startDate: string; // ISO string
endDate: string; // ISO string
color: 'blue' | 'green' | 'red' | 'yellow' | 'purple' | 'orange' | 'gray';
user: IUser;
location?: IEventLocation; // optional โ see "Event locations" below
}
interface IUser {
id: string;
name: string;
picturePath: string | null;
}
// A location is either an online meeting or a physical place โ never a mix of
// both, so it is modeled as a discriminated union.
type IEventLocation =
| { type: 'online'; url: string }
| { type: 'physical'; address: string; lat?: number; lng?: number };
Note:
src/lib/calendar/mocks.tsgenerates its events withMath.random()at module load, so only import it from a serverload. Pulling it into a component would generate different events on the server and the client, and hydration would tear.
Creating events
AddEventDialog validates with zod and, on submit, calls calendar.addEvent(), which appends the event, records an undo snapshot, and writes through to localStorage. To persist to a backend instead, swap the body of addEvent/updateEvent/deleteEvent on CalendarState โ every mutation already routes through them.
Project structure
src/
โโโ routes/
โ โโโ +layout.svelte # ModeWatcher + CalendarProvider
โ โโโ layout.css # Tailwind v4 theme, custom utilities
โ โโโ {day,week,month,year,agenda}-view/
โโโ lib/
โ โโโ calendar/
โ โ โโโ components/
โ โ โ โโโ agenda-view/
โ โ โ โโโ dialogs/ # Add / edit / details dialogs
โ โ โ โโโ dnd/ # Drag, resize, drag-to-create
โ โ โ โโโ header/
โ โ โ โโโ month-view/
โ โ โ โโโ week-and-day-view/
โ โ โ โโโ year-view/
โ โ โ โโโ command-palette.svelte
โ โ โ โโโ keyboard-shortcuts.svelte
โ โ โโโ contexts/ # CalendarState class ($state runes)
โ โ โโโ helpers.ts # Pure date/layout math
โ โ โโโ history.ts # Snapshot undo/redo stacks
โ โ โโโ interfaces.ts
โ โ โโโ notifications.ts # Undoable toasts
โ โ โโโ schemas.ts # zod event schema
โ โ โโโ storage.ts # Versioned localStorage codec
โ โ โโโ types.ts
โ โโโ components/ui/ # shadcn-svelte + two custom components
โ โโโ hooks/
Event locations
Where an event happens matters as much as when โ a Zoom link and a street address are not the same thing. Every event can carry an optional location, modeled as a discriminated union so an online meeting and a physical place never blur together:
- Online โ a meeting link. The platform name and favicon are derived from the URL's host, so Zoom, Google Meet, Teams, Jitsi, Webex, Whereby, or a self-hosted link all work without a hard-coded provider list. The details dialog shows a one-click Join button.
- Physical โ a free-text address (any country). The details dialog renders it on an embedded Leaflet map, geocoded on the fly from the address.
Design notes:
- Optional and non-breaking. Events without a location behave exactly as before; an empty location is stripped on submit, so nothing is stored until you actually fill it in.
- The map never touches SSR. Leaflet is loaded from a CDN inside
onMount, so it never enters the server module graph โ the same client-only pattern the calendar shell already uses for its shimmer library. No new npm dependency is added, and the server bundle contains zero map code. - Favicons come from a public icon service keyed on the link's host, which falls back to a generic globe for unknown hosts, so the icon never renders broken.
Locations are geocoded on display via OpenStreetMap's Nominatim (public endpoint, ~1 req/sec). A production deployment would want a dedicated geocoding key and to persist the resulting
lat/lngโ theIEventLocationtype already carries the optional fields for it.
Roadmap
- Persist badge variant and working hours across reloads.
- Persist geocoded
lat/lngand swap Nominatim for a keyed geocoding service. - Component tests (vitest + vitest-browser-svelte are already configured).
Contributing
Contributions are welcome โ issues and pull requests both. Please run bun run check and bun run lint before submitting.
Credits
A Svelte reimagining of an idea by Leonardo Ramos โ MIT.
Built with SvelteKit, shadcn-svelte, bits-ui, date-fns, and Tailwind CSS.
License
MIT. ยฉ 2026 Rohit Kushwaha, with ยฉ 2025 Leonardo Ramos for the earlier work this builds on โ both notices are retained in the LICENSE file, as MIT requires.














