Getting Started

Quick Start

Five steps from an empty page to a calendar that saves. Each step adds a few lines to the one before it; the new lines are marked in the code.

1. Bind an array

Naive ISO strings are read in the time zone you give the scheduler, and end is exclusive. Drag a chip: the array under the calendar changes with it.

Oct 19 – 25, 2026

GMT+7
Mon 19
Tue 20
Wed 21
Thu 22
Fri 23
Sat 24
Sun 25
All day
[
  {
    "id": "planning",
    "start": "2026-10-19T10:00",
    "end": "2026-10-19T11:30"
  },
  {
    "id": "design",
    "start": "2026-10-20T13:00",
    "end": "2026-10-20T14:30"
  },
  {
    "id": "gym",
    "start": "2026-10-21T18:30",
    "end": "2026-10-21T19:30"
  },
  {
    "id": "offsite",
    "start": "2026-10-22",
    "end": "2026-10-24"
  }
]
<script lang="ts">
  import { Scheduler, type EventInput } from '@sv5ui/scheduler';

  // Naive ISO strings are read in the scheduler's timeZone. end is exclusive:
  // an all-day event on the 17th ends on the 18th.
  let events = $state<EventInput[]>([
    { id: 'planning', title: 'Sprint planning', start: '2026-09-14T10:00', end: '2026-09-14T11:30' },
    { id: 'design', title: 'Design review', start: '2026-09-15T13:00', end: '2026-09-15T14:30' },
    { id: 'offsite', title: 'Team offsite', start: '2026-09-17', end: '2026-09-19', allDay: true }
  ]);
</script>

<Scheduler bind:events timeZone="Asia/Ho_Chi_Minh" height={520} />

<p>{events.length} events</p>

2. Pick a view

view and date are bindable, so the toolbar and controls of your own move the same state. date is a ZonedDateTime, which is why the step imports now from @internationalized/date.

view: month 2026-10-20T10:18:34.081+07:00[Asia/Ho_Chi_Minh]

October 2026

Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday
28
29
30
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
1
<script lang="ts">
  import { now } from '@internationalized/date';
  import { Scheduler, type EventInput } from '@sv5ui/scheduler';

  const zone = 'Asia/Ho_Chi_Minh';
  let events = $state<EventInput[]>([/* ... */]);
  let view = $state('month');                 // new
  let date = $state(now(zone));               // new: a ZonedDateTime
</script>

<!-- Both are bindable, so the toolbar and your own controls move the same state -->
<Scheduler bind:events bind:view bind:date timeZone={zone} height={520} />

<Button label="Next month" onclick={() => (date = date.add({ months: 1 }))} />
<Button label="Agenda" onclick={() => (view = 'agenda')} />

3. Save changes

onMutate runs after the change is already on screen. Here it waits 600ms and shows a toast; throwing instead would slide the event back. Move two events quickly: each has its own queue. The Saving Changes page covers rollback, conflicts and server-assigned ids.

Idle

Oct 19 – 25, 2026

GMT+7
Mon 19
Tue 20
Wed 21
Thu 22
Fri 23
Sat 24
Sun 25
All day
<script lang="ts">
  import { toast } from 'sv5ui';
  import { Scheduler, type EventInput, type Mutation } from '@sv5ui/scheduler';

  let events = $state<EventInput[]>([/* ... */]);

  // Called after the change is already on screen. Throw to roll it back.
  async function onMutate(mutation: Mutation) {                       // new
    await fetch('/api/events', { method: 'POST', body: JSON.stringify(mutation) });
    toast(`Saved ${mutation.kind} of ${mutation.after?.title ?? mutation.before?.title}`);
  }
</script>

<Scheduler bind:events timeZone="Asia/Ho_Chi_Minh" height={520} {onMutate} />

4. Group into calendars

Events join a calendar through calendarId and take its colour. sidebar adds the date navigator, search and a checklist bound to hiddenCalendars. Untick Personal in the sidebar and hiddenCalendars updates.

hiddenCalendars: []

Oct 19 – 25, 2026

GMT+7
Mon 19
Tue 20
Wed 21
Thu 22
Fri 23
Sat 24
Sun 25
All day
Lunch break
<script lang="ts">
  import { Scheduler, type EventInput, type SchedulerCalendar } from '@sv5ui/scheduler';

  const calendars: SchedulerCalendar[] = [                            // new
    { id: 'work', title: 'Work', color: 'primary' },
    { id: 'personal', title: 'Personal', color: 'success' }
  ];

  // Events join a calendar through calendarId and take its colour
  let events = $state<EventInput[]>([
    { id: 'planning', title: 'Sprint planning', calendarId: 'work', start: '...', end: '...' },
    { id: 'gym', title: 'Gym', calendarId: 'personal', start: '...', end: '...' }
  ]);
  let hiddenCalendars = $state<string[]>([]);                         // new
</script>

<!-- sidebar adds a date navigator, search and the calendar checklist -->
<Scheduler bind:events bind:hiddenCalendars {calendars} sidebar timeZone="Asia/Ho_Chi_Minh" height={560} />

5. Your own form

With createPanel set the scheduler never creates an event by itself. Drag across empty slots or press Enter on a focused slot and your snippet opens with the picked range; create() saves through the same pipeline as a drag. A full form with a calendar picker, dates and notes is on the Creating & Editing page.

Oct 19 – 25, 2026

GMT+7
Mon 19
Tue 20
Wed 21
Thu 22
Fri 23
Sat 24
Sun 25
All day
<script lang="ts">
  import { Button, Input } from 'sv5ui';
  import { Scheduler, type EventInput } from '@sv5ui/scheduler';

  let events = $state<EventInput[]>([/* ... */]);
  let title = $state('');
</script>

<!-- With createPanel set, dragging across empty slots opens your form
     instead of creating "New event". create() saves through onMutate. -->
<Scheduler bind:events timeZone="Asia/Ho_Chi_Minh" height={520} createPanel={form} />

{#snippet form({ start, end, allDay, close, create })}
  <Input bind:value={title} placeholder="What is it about?" autofocus />
  <div class="flex gap-2">
    <Button label="Save" onclick={() => { create({ title, start, end, allDay }); title = ''; }} />
    <Button label="Cancel" variant="ghost" color="surface" onclick={close} />
  </div>
{/snippet}

Where to Next

Props

The props the five steps use.

PropTypeDefault
eventsEventInput<T>[]-
calendarsSchedulerCalendar[][]
onMutate(m: Mutation<T>) => MutationResult<T> | Promise<...>-
sidebarboolean | Snippet<[SidebarSnippetProps<T>]>false
timeZoneTimeZoneIdlocal zone
heightnumber | string-
createPanelSnippet<[CreatePanelSnippetProps<T>]>-

Snippets

What the createPanel snippet in step 5 receives.

TypeFields
CreatePanelSnippetPropsstart, end, allDay, close, create