Installation
Add the scheduler to a SvelteKit project that already runs sv5ui.
Install the package
The scheduler ships separately from sv5ui and needs sv5ui 2.7.0 or later beside it. svelte and tailwindcss are peer dependencies, so the two packages share one copy of each. @internationalized/date, tailwind-variants and tailwind-merge come with the package and are the same versions sv5ui already carries.
npm install @sv5ui/scheduler sv5uiImport both themes
Tailwind 4 skips node_modules when it scans for class names, so a package has to register its own compiled output. The scheduler theme file is one @source line that does exactly that. It has to sit in the stylesheet that imports Tailwind itself, which sv5ui/theme.css already does.
@import 'tailwindcss';
@import 'sv5ui/theme.css';
@import '@sv5ui/scheduler/theme.css';
@custom-variant dark (&:where(.dark, .dark *));Give it a height
The scheduler fills whatever height its layout gives it and scrolls inside. The common case is the rest of the page: put it in a flex column under your header with min-h-0 flex-1. A fixed height prop, or a parent with a height, works for a card or a dashboard tile.
<script lang="ts">
import { ModeWatcher } from 'mode-watcher';
import './layout.css';
let { children } = $props();
</script>
<ModeWatcher />
<div class="flex h-dvh flex-col">
<header class="h-14 shrink-0 border-b">Your app header</header>
{@render children()}
</div>Render it
Bind an array and you have a working calendar. Every change the user makes through the UI is written back into that array. Set timeZone so the page renders the same on the server as it does in the browser.
<script lang="ts">
import { Scheduler, type EventInput } from '@sv5ui/scheduler';
let events = $state<EventInput[]>([
{ id: 'standup', title: 'Standup', start: '2026-09-14T09:00', end: '2026-09-14T09:15' },
{ id: 'offsite', title: 'Team offsite', start: '2026-09-17', end: '2026-09-19', allDay: true }
]);
</script>
<Scheduler bind:events timeZone="Asia/Ho_Chi_Minh" class="min-h-0 flex-1" />Requirements
| Package | Version |
|---|---|
Svelte | 5.x |
Tailwind CSS | 4.x |
sv5ui | 2.7.0 or later |
@internationalized/date | 3.11 or later |
Entry Points
Three imports cover the package.
| Entry | What it holds |
|---|---|
@sv5ui/scheduler | Scheduler, the five views, the sidebar parts, EventChip, dragSource, config and every type |
@sv5ui/scheduler/locales | Thirteen label packs, one export each: ar, de, en, es, fr, it, ja, ko, nl, pt, ru, vi, zh |
@sv5ui/scheduler/theme.css | One @source line that lets Tailwind see the classes inside the package |
import { Scheduler, dragSource, defineSchedulerConfig } from '@sv5ui/scheduler';
import type { EventInput, Mutation, SchedulerLabels } from '@sv5ui/scheduler';
// Label packs live behind their own entry, so a bundle only carries the ones it imports
import { vi, ja } from '@sv5ui/scheduler/locales';Dates You Build Yourself
Every date the scheduler hands back is a ZonedDateTime from @internationalized/date, the same library sv5ui's date pickers use. Passing ISO strings in and reading values out
needs nothing more than the scheduler itself. The moment your own code constructs or types
one of those values, install the package directly, and keep its version range in step with
the scheduler's so the bundle holds one copy.
// The scheduler hands you ZonedDateTime values and accepts them back.
// Passing ISO strings needs nothing extra:
const events = [{ id: 'a', title: 'Call', start: '2026-09-14T09:00', end: '2026-09-14T09:30' }];
// Building or typing one yourself imports the package that defines it:
import { now, parseZonedDateTime, type ZonedDateTime } from '@internationalized/date';
let date = $state<ZonedDateTime>(now('Asia/Ho_Chi_Minh'));
const nextWeek = parseZonedDateTime('2026-09-21T09:00[Asia/Ho_Chi_Minh]');
// Under pnpm a package that only arrives as a dependency of a dependency
// cannot be imported, so add it to your own package.json:
// pnpm add @internationalized/dateApp-wide Defaults
Set the slot classes every scheduler in the app starts from. A scheduler reads the config
when it is created, so the call has to run before any scheduler renders. Keep it in src/scheduler.config.ts and import that file once from the root layout, the same way an app keeps sv5ui's own defaults
in a config file of its own. Each call replaces the previous config rather than merging into it.
The Theming page lists what can be set.
// src/scheduler.config.ts
import { defineSchedulerConfig } from '@sv5ui/scheduler';
defineSchedulerConfig({
scheduler: { slots: { root: 'rounded-2xl border border-outline-variant/60' } },
eventChip: { slots: { title: 'font-medium' } }
});<!-- src/routes/+layout.svelte -->
<script lang="ts">
import '../scheduler.config';
</script>Rendering on the Server
The scheduler renders on the server without touching window.
The one thing to decide is the time zone: its default is the zone of the machine doing the
rendering, which is not the reader's when that machine is a server. See Time Zones for what the zone changes.
<!-- Without timeZone the scheduler uses the zone of the machine that renders:
UTC on most servers, the reader's zone in the browser. The two renders
then disagree about which day is today and where the current-time line
sits, and hydration repaints the calendar. Pass the zone the user
actually lives in, from their profile or a cookie. -->
<Scheduler bind:events timeZone={data.user.timeZone} />Troubleshooting
The scheduler prints a warning once per cause in development instead of failing, so the console is the first place to look. These are the messages, and below them the setup problems that show up without one.
// Console messages the scheduler prints in development, and what they mean.
// An event whose end is not after its start, or whose dates cannot be parsed,
// is left out rather than breaking the calendar. Once per event id.
'[@sv5ui/scheduler] Event x was skipped: Event x must end after it starts'
// dayStartHour and dayEndHour must be whole hours inside one day, start before end.
'[@sv5ui/scheduler] Visible hours 8 to 8 are not a range inside one day; showing the whole day.'
// hiddenDays lists every day of the week.
'[@sv5ui/scheduler] hiddenDays hides every day of the week; showing all of them.'
// A recurrence rule uses fields the built-in engine does not evaluate.
'[@sv5ui/scheduler] Event "x" uses recurrence fields the built-in engine does not evaluate: byMonthDay.'
// Thrown, not printed: view names the scheduler does not know.
'Unknown view "workweek". Known: day, week, month, year, agenda'