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
| Field | Description |
|---|---|
id | Stable and unique. Mutations are keyed by it, so it must not change while the event exists |
title | Shown on the chip and read to screen readers |
start | See the forms below. A naive string is read in timeZone |
end | Exclusive. Must be after start; an all-day event on one day ends on the next |
allDay | All-day row and month cells rather than the time grid |
calendarId | Joins one of calendars: colour, grouping, hiding |
color | Overrides the calendar colour. primary when neither is set |
background | A translucent block behind other events. Never editable, never opens details |
editable | false locks this event against move, resize, delete and update |
recurrence | Makes the event a series. See Recurring Events |
seriesId | Set by the scheduler on an occurrence: the id of its series. Never set it yourself |
resourceId | Stored and handed back. No built-in view reads it |
data | Your 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
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
| Form | Example |
|---|---|
Naive | 2026-09-16T09:00 |
UTC | 2026-09-16T02:00Z |
Offset | 2026-09-16T09:00+07:00 |
Zone suffix | 2026-09-16T09:00[Asia/Ho_Chi_Minh] |
Date only | 2026-09-16 |
ZonedDateTime | parseZonedDateTime('...') |
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
// 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.
Oct 19 – 25, 2026
// 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.
Oct 19 – 25, 2026
// 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 refetchLocking 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
<!-- 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.
| Prop | Type | Default |
|---|---|---|
events | EventInput<T>[] | - |
timeZone | TimeZoneId | local zone |
calendars | SchedulerCalendar[] | [] |
ref | HTMLElement | null | null |