Events & Data

Events

An event is a plain object with an id, a title and two dates. Everything else is optional, and the scheduler never reads what it does not know.

The Event Shape

EventInput<T> is what you pass in. Inside the scheduler it becomes a SchedulerEvent<T>, the same shape with both dates as ZonedDateTime; that is what every snippet and callback receives.

import type { EventInput } from '@sv5ui/scheduler';

const event: EventInput = {
  id: 'design',                      // stable and unique; saves are keyed by it
  title: 'Design review',
  start: '2026-09-15T13:00',         // read in the scheduler's timeZone
  end: '2026-09-15T14:30',           // exclusive
  calendarId: 'work',                // colour and grouping from one of `calendars`
  color: 'tertiary',                 // optional override of the calendar colour
  editable: true,                    // false locks it
  data: { room: 'Aurora' }           // anything; handed back untouched
};

Fields

FieldDescription
idStable and unique. Mutations are keyed by it, so it must not change while the event exists
titleShown on the chip and read to screen readers
startSee the forms below. A naive string is read in timeZone
endExclusive. Must be after start; an all-day event on one day ends on the next
allDayAll-day row and month cells rather than the time grid
calendarIdJoins one of calendars: colour, grouping, hiding
colorOverrides the calendar colour. primary when neither is set
backgroundA translucent block behind other events. Never editable, never opens details
editablefalse locks this event against move, resize, delete and update
recurrenceMakes the event a series. See Recurring Events
seriesIdSet by the scheduler on an occurrence: the id of its series. Never set it yourself
resourceIdStored and handed back. No built-in view reads it
dataYour payload. Never read, compared or copied by the scheduler

Dates You Can Pass

Five events, one slot. Only the naive string depends on the scheduler's zone: it means 9:00 on whatever clock the calendar shows. The others name an instant and are converted.

October 20, 2026

Tuesday

October 20, 2026

Today GMT+7
// Five ways to write 9:00 in Ho Chi Minh City on 16 September. All land on the same slot.
const events: EventInput[] = [
  { id: 'naive',  title: 'Naive string',    start: '2026-09-16T09:00',        end: '2026-09-16T09:45' },
  { id: 'utc',    title: 'UTC, with Z',     start: '2026-09-16T02:00Z',       end: '2026-09-16T02:45Z' },
  { id: 'offset', title: 'With an offset',  start: '2026-09-16T09:00+07:00',  end: '2026-09-16T09:45+07:00' },
  { id: 'zoned',  title: 'With a zone',     start: '2026-09-16T09:00[Asia/Ho_Chi_Minh]', end: '2026-09-16T09:45[Asia/Ho_Chi_Minh]' },
  { id: 'object', title: 'ZonedDateTime',   start: parseZonedDateTime('2026-09-16T11:00[Asia/Tokyo]'),
                                             end: parseZonedDateTime('2026-09-16T11:45[Asia/Tokyo]') }
];

// A naive string is the only form that depends on timeZone: it means 9:00 on
// the clock of whatever zone the scheduler shows. The other four name an instant.

Forms

FormExample
Naive2026-09-16T09:00
UTC2026-09-16T02:00Z
Offset2026-09-16T09:00+07:00
Zone suffix2026-09-16T09:00[Asia/Ho_Chi_Minh]
Date only2026-09-16
ZonedDateTimeparseZonedDateTime('...')

All-Day and Multi-Day

allDay puts an event in the all-day row and across month cells. Because end is exclusive, one day ends on the next day and three days end on the fourth. A timed event that lasts twenty four hours or more is drawn the same way; a shorter one across midnight stays in the grid, split at the day boundary.

Oct 19 – 25, 2026

GMT+7
Mon 19
Tue 20
Wed 21
Thu 22
Fri 23
Sat 24
Sun 25
All day
// end is exclusive, so a single day ends on the next day
{ id: 'holiday', title: 'Founders day', start: '2026-09-15', end: '2026-09-16', allDay: true }

// Three days: the 17th, 18th and 19th
{ id: 'offsite', title: 'Team offsite', start: '2026-09-17', end: '2026-09-20', allDay: true }

// A timed event that lasts a day or more is drawn like an all-day one
{ id: 'oncall', title: 'On call', start: '2026-09-14T09:00', end: '2026-09-15T09:00' }

// A timed event across midnight stays in the time grid, split over two columns
{ id: 'release', title: 'Release window', start: '2026-09-18T22:00', end: '2026-09-19T01:00' }

Colours

An event takes its own color, else the colour of its calendar, else primary. The eight roles and the tokens behind them are on the Theming page; calendars are on Calendars & Filtering.

Your Own Data

data carries whatever your app needs. The scheduler stores it, hands it back on every event it gives you, and never reads it. Type the array as EventInput<Meeting>[] and the type reaches every snippet argument and callback.

<script lang="ts">
  import { Scheduler, type EventInput, type EventSnippetProps } from '@sv5ui/scheduler';

  interface Meeting {
    room: string;
    attendees: string[];
  }

  // Typing the array is enough: T flows into every snippet and callback
  let events = $state<EventInput<Meeting>[]>([
    { id: 'kickoff', title: 'Kickoff', start: '2026-09-14T09:30', end: '2026-09-14T11:00',
      data: { room: 'Aurora', attendees: ['Linh', 'Minh'] } }
  ]);
</script>

<Scheduler bind:events event={card} onEventClick={(event) => console.log(event.data?.room)} />

{#snippet card({ event }: EventSnippetProps<Meeting>)}
  <div class="h-full rounded bg-primary-container p-1 text-xs">
    {event.title} · {event.data?.room}
  </div>
{/snippet}

What bind:events Writes Back

Until the first change, the array is exactly yours. After a change the scheduler writes every event back as it holds it: dates become ZonedDateTime values in timeZone, for the events that did not move as well. Series keep their rule. Drag Sprint planning and watch the first element change type.

events[0].start is a string 2026-10-19T10:00

Oct 19 – 25, 2026

GMT+7
Mon 19
Tue 20
Wed 21
Thu 22
Fri 23
Sat 24
Sun 25
// Before any change, the array is exactly what you passed:
events[0].start            // '2026-09-14T10:00'  (your string)

// After the user drags that event, the whole array is rewritten from the store:
events[0].start            // ZonedDateTime 2026-09-14T11:00:00+07:00[Asia/Ho_Chi_Minh]
events[1].start            // ZonedDateTime too, even though it did not move

// Sending it on
events[0].start.toAbsoluteString()   // '2026-09-14T04:00:00.000Z'  for an API that stores UTC
events[0].start.toString()           // '2026-09-14T11:00:00+07:00[Asia/Ho_Chi_Minh]'

Changing Events From Outside

The array is reactive state, so push, filter, splice or reassign it and the calendar follows. None of that is a user gesture: nothing goes through onMutate, because the app already knows what it changed. A refetch that lands while a save is in flight does not undo the save; see Saving Changes.

4 events

Oct 19 – 25, 2026

GMT+7
Mon 19
Tue 20
Wed 21
Thu 22
Fri 23
Sat 24
Sun 25
// The array is $state, so the usual operations reach the calendar.
// None of them is a user gesture, so onMutate is not called.
events.push({ id: 'lunch', title: 'Lunch', start: '2026-09-17T12:00', end: '2026-09-17T13:00' });
events = events.filter((event) => event.id !== 'gym');
events = await (await fetch('/api/events')).json();    // a refetch

Locking Events

editable: false on an event stops it moving, resizing, being deleted or updated, and takes the delete button out of its details. editable={false} on the scheduler does that for every event; creating new ones follows creatable separately. Background blocks and the occurrences of a series are locked whatever you set.

Oct 19 – 25, 2026

GMT+7
Mon 19
Tue 20
Wed 21
Thu 22
Fri 23
Sat 24
Sun 25
Lunch break
<!-- One event: no move, resize, delete or update; the popover loses its delete button -->
{ id: 'board', title: 'Board meeting', start: '...', end: '...', editable: false }

<!-- Every event, whatever it says for itself. Creating follows creatable instead. -->
<Scheduler bind:events editable={false} />

<!-- Background blocks and occurrences of a series are always locked -->

Events That Cannot Be Read

An event whose end is not after its start, or whose date string does not parse, is left out of the calendar with one warning per id in development. The rest of the array renders. The same rule applies to what source returns.

// Skipped with one warning in development, never thrown:
{ id: 'x', title: 'Ends first', start: '2026-09-15T10:00', end: '2026-09-15T09:00' }
// [@sv5ui/scheduler] Event x was skipped: Event x must end after it starts

// An event that lies entirely outside dayStartHour and dayEndHour stays in the
// array and is simply not drawn.

Props

The props this page uses. events is bindable.

PropTypeDefault
eventsEventInput<T>[]-
timeZoneTimeZoneIdlocal zone
calendarsSchedulerCalendar[][]
refHTMLElement | nullnull