Multi-calendar overlay
Several calendars overlaid on one week - Work, Personal, Family, Holidays - each colour-coded from a legend you can toggle. Turning a calendar off hides its events across every view; an event takes its calendar colour. (requires @svgrid/enterprise)
A live, editable Svelte 5 data grid example from the SvGrid gallery (Scheduler). See the SvGrid documentation for the full API.
About this example
Several calendars overlaid on one week in the Svelte 5 scheduler: Work, Personal, Family and Holidays, each colour-coded from a legend you can toggle, through calendars and calendarField. Turning a calendar off hides its events across every view, and an event takes its calendar's colour unless it sets its own; the same rows power the Table.
Several calendars overlaid on one week - Work, Personal, Family, Holidays - each colour-coded from a legend you can toggle. Turning a calendar off hides its events across every view; an event's colour comes from its calendar unless it sets its own. The same grid rows power the Table. Renderer: @svgrid/enterprise.
Imports, features and API used
Imports: @svgrid/grid, @svgrid/enterprise
Columns: title (Event), cal (Calendar), start (Start), end (End)
Frequently asked questions
How is an event assigned to a calendar?
calendarField names the row field holding the calendar id, and calendars lists each id with a title and colour; the legend renders from that list.
How does the toggle hide events?
Each calendar has a visible flag the legend flips; the scheduler filters events whose calendar is hidden in every view without touching the rows.
Can an event override its colour?
Yes. An event with its own colour keeps it; otherwise the calendar's colour from the calendars list applies through calendarField.
Related documentation
Related articles
- Multi-Level (Grouped) Column Headers in SvGrid - Band related columns under a shared parent header using SvGrid's nested column definition - how to nest, pin, combine with sorting and filtering, and when NOT to use grouping.
- Multi-Column Sorting in a Svelte Data Grid - How multi-column sorting works in SvGrid, from click behavior and sort priority to server-side ordering and programmatic control via the API.
Source code (397-scheduler-multi-calendar.svelte)
<script lang="ts">
/**
* 397. Multi-calendar overlay (Enterprise Scheduler Pro)
* -----------------------------------------------------
* Several calendars overlaid on one week - Work, Personal, Family, Holidays -
* each colour-coded from a legend you can toggle. Turning a calendar off hides
* its events across every view; an event's colour comes from its calendar unless
* it sets its own. The same grid rows power the Table. Renderer: @svgrid/enterprise.
*/
import { SvGrid, type ColumnDef, type SchedulerEventMoveEvent, type SchedulerEventResizeEvent, type SchedulerEventCommitEvent } from '@svgrid/grid'
import { enableSchedulerView, type SchedulerProConfig } from '@svgrid/enterprise'
enableSchedulerView()
const calendars = [
{ id: 'work', title: 'Work', color: '#4f46e5' },
{ id: 'personal', title: 'Personal', color: '#16a34a' },
{ id: 'family', title: 'Family', color: '#db2777' },
{ id: 'holidays', title: 'Holidays', color: '#d97706' },
]
type Ev = { id: string; title: string; cal: string; start: string; end: string; allDay?: boolean }
const pad = (n: number) => String(n).padStart(2, '0')
const iso = (d: Date) => `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`
const monday = new Date(); monday.setHours(0, 0, 0, 0); monday.setDate(monday.getDate() - ((monday.getDay() + 6) % 7))
const at = (day: number, h: number, m = 0) => { const d = new Date(monday); d.setDate(monday.getDate() + day); d.setHours(h, m, 0, 0); return iso(d) }
let seq = 100
let rows = $state<Ev[]>([
{ id: 'e1', title: 'Standup', cal: 'work', start: at(0, 9), end: at(0, 9, 30) },
{ id: 'e2', title: 'Design review', cal: 'work', start: at(1, 11), end: at(1, 12) },
{ id: 'e3', title: '1:1 with Sam', cal: 'work', start: at(2, 14), end: at(2, 14, 30) },
{ id: 'e4', title: 'Sprint planning', cal: 'work', start: at(4, 13), end: at(4, 14) },
{ id: 'e5', title: 'Gym', cal: 'personal', start: at(0, 18), end: at(0, 19) },
{ id: 'e6', title: 'Dentist', cal: 'personal', start: at(2, 10), end: at(2, 11) },
{ id: 'e7', title: 'Book club', cal: 'personal', start: at(3, 19), end: at(3, 20, 30) },
{ id: 'e8', title: 'Lunch with Alex', cal: 'family', start: at(2, 12, 30), end: at(2, 13, 30) },
{ id: 'e9', title: "Mom's birthday", cal: 'family', start: at(5, 0), end: at(6, 0), allDay: true },
{ id: 'e10', title: 'Public holiday', cal: 'holidays', start: at(0, 0), end: at(1, 0), allDay: true },
{ id: 'e11', title: 'School closed', cal: 'holidays', start: at(4, 0), end: at(5, 0), allDay: true },
])
const columns: ColumnDef<any, Ev>[] = [
{ field: 'title', header: 'Event', editorType: 'text', width: 170 },
{ field: 'cal', header: 'Calendar', editorType: 'list', editorOptions: calendars.map((c) => ({ value: c.id, label: c.title, color: c.color })), width: 120 },
{ field: 'start', header: 'Start', editorType: 'datetime', width: 160 },
{ field: 'end', header: 'End', editorType: 'datetime', width: 160 },
]
function onEventMove(e: SchedulerEventMoveEvent<Ev>) { e.row.start = iso(e.start); e.row.end = iso(e.end) }
function onEventResize(e: SchedulerEventResizeEvent<Ev>) { e.row.start = iso(e.start); e.row.end = iso(e.end) }
function onEventCommit(e: SchedulerEventCommitEvent<Ev>) { Object.assign(e.row, e.values) }
function onEventAdd(start: Date, end: Date) { rows = [...rows, { id: `e${++seq}`, title: 'New event', cal: 'work', start: iso(start), end: iso(end) }] }
function onEventDelete(row: Ev) { rows = rows.filter((r) => r !== row) }
let view = $state<'timeline' | 'table'>('timeline')
const schedulerCfg: SchedulerProConfig<any, Ev> = {
startField: 'start', endField: 'end', titleField: 'title', allDayField: 'allDay',
calendars, calendarField: 'cal',
views: ['week', 'month', 'day', 'agenda'], initialView: 'week', weekStartsOn: 1,
dayStartHour: 7, dayEndHour: 21, slotMinutes: 30,
editable: true, tooltip: true, drawer: true,
onEventMove, onEventResize, onEventCommit, onEventAdd, onEventDelete,
}
</script>
<section class="mc">
<header class="mc-head">
<div class="mc-title">
<strong>My calendars</strong>
<span class="mc-sub">Toggle a calendar in the legend to overlay or hide it</span>
</div>
<div class="mc-seg" role="tablist" aria-label="View">
<button class="mc-seg-btn" role="tab" aria-selected={view === 'timeline'} class:mc-on={view === 'timeline'} onclick={() => (view = 'timeline')}>Calendar</button>
<button class="mc-seg-btn" role="tab" aria-selected={view === 'table'} class:mc-on={view === 'table'} onclick={() => (view = 'table')}>Table</button>
</div>
</header>
<div class="mc-body">
{#if view === 'timeline'}
<SvGrid
columnResize data={rows} columns={columns} getRowId={(r) => r.id} containerHeight="100%" scheduler={schedulerCfg} />
{:else}
<SvGrid
columnResize data={rows} columns={columns} getRowId={(r) => r.id} containerHeight="100%" editable />
{/if}
</div>
</section>
<style>
.mc { display: flex; flex: 1 1 auto; flex-direction: column; min-height: 0; border: 1px solid var(--sg-border, #e5e7eb); border-radius: 12px; overflow: hidden; background: var(--sg-bg, #fff); }
.mc-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 14px; border-bottom: 1px solid var(--sg-border, #e5e7eb); }
.mc-title { display: flex; flex-direction: column; gap: 2px; }
.mc-sub { font-size: 0.78rem; color: var(--sg-muted, #6b7280); }
.mc-seg { display: inline-flex; border: 1px solid var(--sg-border, #e5e7eb); border-radius: 8px; overflow: hidden; }
.mc-seg-btn { border: 0; background: var(--sg-bg, #fff); color: var(--sg-muted, #6b7280); font: inherit; font-size: 0.82rem; padding: 5px 12px; cursor: pointer; }
.mc-seg-btn.mc-on { background: var(--sg-accent, #4f46e5); color: #fff; }
.mc-body { flex: 1 1 auto; min-height: 0; padding: 8px; }
</style>More Scheduler examples
- Scheduler / calendar mode - The same <SvGrid>, same data + columns, rendered as a full calendar by setting one `scheduler` prop: rows become events bucketed by time. Switch Month / Week / Day / Agenda in the toolbar; drag an event to reschedule, drag its edge to resize, click to edit in a drawer. onEventMove / onEventResize write the new times back onto your row. Toggle Calendar / Table to see it is one grid. The calendar renderer ships in @svgrid/enterprise.
- Timeline views (Day / Week) - A horizontal resource timeline: teams are rows, time runs left→right on a scrollable axis, work items are bars sized to real durations (half-day blocks up to multi-day tasks). Switch Day (hour ticks) or Week (day ticks); drag a bar to re-time it, to another team's row to reassign, or an edge to resize. Click / drag empty space to select a range (arrows move, Shift+arrows extend, Enter creates); Ctrl-click bars to multi-select. Bar fill = team, left strip = status.
- Selecting cells & events - Drag empty slots to create events (drag across days for a rectangle - one per day); Ctrl/Cmd-click events to multi-select (Shift-click a range), drag the set to move it together, or press Delete to remove. Enterprise scheduler view.
- Horizon - Outlook-style calendar app - A full calendar client on the Scheduler + SvDockManager: NavPane module rail + mini date-picker + calendar toggles, and a dockable workspace (drag / resize / float panes) with the calendar over an Upcoming grid and an Inbox. Recurring events with per-occurrence edits, a second time-zone ruler, iCal import/export and undo.
- Meridian Clinic - appointment scheduling - A front-desk appointment console: providers with their own hours as day columns, a drag-in patient waitlist, a utilization gauge, and a live patient-flow pipeline (Waiting / Checked in / In room / Completed) that updates as you work.