Scheduler
A calendar component for Svelte 5. Month, week, day, year and agenda views, drag and drop, recurring events, time zones and a save pipeline that shows the change first and rolls it back if the server says no. Bind an array of events and you have a working calendar.
bun add @sv5ui/scheduler and ships on its own schedule, which has nothing to do with v2.9.0 of sv5ui. Basic Example
A team's week with three calendars and the built-in sidebar. Drag a chip, pull its edge,
drag across empty slots to create one, or drop an item from the list on the left. Each
gesture is written back into the bound array and reported to onMutate, which the line under the calendar reads from.
Oct 19 – 25, 2026
<script lang="ts">
import { Scheduler, type EventInput, type SchedulerCalendar } from '@sv5ui/scheduler';
const calendars: SchedulerCalendar[] = [
{ id: 'work', title: 'Work', color: 'primary' },
{ id: 'team', title: 'Team', color: 'info' },
{ id: 'personal', title: 'Personal', color: 'success' }
];
// A plain array. Drag a chip and it changes; push into it and the calendar changes.
let events = $state<EventInput[]>([
{ id: 'planning', title: 'Sprint planning', calendarId: 'team',
start: '2026-09-14T10:00', end: '2026-09-14T11:30' },
{ id: 'offsite', title: 'Team offsite', calendarId: 'team',
start: '2026-09-17', end: '2026-09-19', allDay: true },
{ id: 'standup', title: 'Daily standup', calendarId: 'team',
start: '2026-09-01T09:00', end: '2026-09-01T09:15',
recurrence: { freq: 'weekly', byDay: [1, 2, 3, 4, 5] } }
]);
</script>
<Scheduler bind:events {calendars} timeZone="Asia/Ho_Chi_Minh" sidebar height={640} />Why This Scheduler
One source of truth
events is a plain bindable array. Push into it and the calendar updates; drag a chip and the array updates. There is no imperative API to learn.
Saving is one function
onMutate receives every change. The UI updates first; a failed save slides the event back, and a different answer from the server goes through onConflict.
Correct time
Every date is a ZonedDateTime. Events land on the right instant in any zone and keep their length on the days clocks change.
Native Svelte 5
Runes, snippets and attachments throughout. The event, the cell, the header, the details, the toolbar and the sidebar are snippets, not render functions.
Accessible
One tab stop per grid, arrow keys between slots, Space to pick an event, Enter to create, every move announced, right to left layouts.
Works on a phone
Long press to drag, a one-row toolbar, three days at a time, a slide-over sidebar and an agenda view for narrow screens.
Extensible by design
Views, layouts, interactions and store middleware are registered per instance through the same interfaces the built-in features use.
Themed by sv5ui
Drawn with sv5ui design tokens, so it follows your theme and dark mode without extra styling. Named slots adjust any part.
What's Inside
One component carries all of it; the pages in the sidebar take one area each.
| Area | What you get |
|---|---|
| Views | Month, week, day, year and agenda; any number of days in a row; hidden days; visible hours |
| Events | All-day and multi-day events, colours, calendars, background blocks, a typed payload of your own |
| Saving | Optimistic updates, per-event queues, rollback with animation, conflict resolution, server-assigned ids |
| Loading | A function that loads only the visible range, with caching and cancelled stale requests |
| Recurrence | Daily, weekly, monthly and yearly series with interval, count, until and skipped dates |
| Interaction | Select, drag to create, move, resize, drop from your own lists, touch, keyboard |
| Editing | A panel for your own form, event details in a popover or a slide-over, hooks for your own dialog |
| Time and language | Any IANA zone, thirteen label packs, 12 or 24 hour clocks, week start, right to left |
| Sidebar | Date navigator, search, calendar checklist and a drag list, docked or as a slide-over |
| Extension | Custom views and layouts, interaction plugins, store middleware |
How It Fits Together
Two paths, one store. Data goes down from your array to the pixels; gestures come back up as mutations. Knowing where a step sits tells you which page explains it and which prop changes it.
// What happens between your array and the pixels, top to bottom.
events (or source) // EventInput[]: ISO strings or ZonedDateTime, any zone
-> normalised to timeZone // every date becomes a ZonedDateTime in one zone
-> event store // indexed by time; the single source of truth
-> hiddenCalendars, search, filter
-> recurring series expanded for the visible range only
-> the view's layout // overlap columns for the time grid, lanes for the month
-> the view draws // snippets replace any part
// And back up again when the user does something.
drag, resize, create, delete, panel update
-> Mutation { kind, before, after }
-> applied to the store at once // the UI already shows the result
-> onMutate(mutation) // your save; may return the server's version
-> confirmed, rolled back with onError, or resolved through onConflict
-> written back into eventsNot Included
So you can decide quickly. Each of these is a real gap rather than a hidden option.
Resource views
Rooms or people as columns or rows. Events carry a resourceId and SchedulerResource is exported so your data has a shape for it, but no built-in view groups by it.
The full RFC 5545 rule set
byMonthDay, bySetPos, ordinal weekdays, rDates and the rest are accepted on the type and ignored with a development warning. What is evaluated is listed on the Recurring Events page.
Editing one occurrence
Occurrences of a series are read only. To skip or end a series, change the rule on the series itself.
Undo and redo, multi-select, copy and paste
Every change is one mutation you can record through onMutate or middleware, but the scheduler keeps no history.
Printing, iCal, exports
The events are your array; what you write out of it is up to you.
Several time zones on one grid
A scheduler shows one zone. Two side by side share the same events, as the Time Zones page shows.
Requirements
Peer dependencies. @internationalized/date, tailwind-variants and tailwind-merge are installed with the package.
| Peer | Version |
|---|---|
svelte | ^5.0.0 |
tailwindcss | ^4.0.0 |
sv5ui | ^2.7.0 |
API Stability
Everything exported from the package root is public and listed on the API Reference. The package is in its 0.x line, where semver allows a minor release to change an export;
each such change is called out in the changelog with what to do about it. Internal parts,
the layout maths, the store and the gesture engine, are not exported and change freely. The data-sch-* attributes on the DOM are part of the public surface, because CSS and tests depend on them.