Theming
The scheduler is drawn with sv5ui theme tokens and nothing else, so it follows your theme and dark mode with no work. What is left to decide is the colour of events, the classes of named slots, and the CSS you want to reach inside the grid with.
Drawn With sv5ui Tokens
Every surface, border and text colour is a Material 3 token from sv5ui: surface, on-surface, outline-variant, primary and their containers. Change the theme with the palette button in the header of this site and
every calendar on the page follows, including the current-time line and the selection ring. See Theming and Dark Mode in the sv5ui docs for the tokens themselves.
Event Colours
Eight colour roles, each a pair of tokens. The time grid draws a soft chip: a container
background with a bar of the full colour on the start edge. The all-day row, the month and
the +N list draw a solid pill. An event takes its own color first, then the colour of its calendar, then primary.
October 20, 2026
October 20, 2026
Today GMT+7<script lang="ts">
import { Scheduler, type EventColor, type EventInput } from '@sv5ui/scheduler';
const colors: EventColor[] = [
'primary', 'secondary', 'tertiary', 'success', 'warning', 'error', 'info', 'surface'
];
// An event takes its own color first, then its calendar's, then primary
let events = $state<EventInput[]>(
colors.map((color, i) => ({
id: color,
title: color,
start: `2026-09-16T${String(8 + i).padStart(2, '0')}:00`,
end: `2026-09-16T${String(8 + i).padStart(2, '0')}:50`,
color
}))
);
</script>
<Scheduler bind:events view="day" timeZone="Asia/Ho_Chi_Minh" creatable={false} height={640} />Colour roles
| Role | Soft (time grid) |
|---|---|
primary | bg-primary-container text-on-primary-container |
secondary | bg-secondary-container text-on-secondary-container |
tertiary | bg-tertiary-container text-on-tertiary-container |
success | bg-success-container text-on-success-container |
warning | bg-warning-container text-on-warning-container |
error | bg-error-container text-on-error-container |
info | bg-info-container text-on-info-container |
surface | bg-surface-container-high text-on-surface |
Per Scheduler
Every part of the frame has a named slot. Pass classes through ui to adjust spacing, borders or surfaces; they are merged with tailwind-merge, so a class you pass
wins over the default of the same kind. class merges into root.
October 2026
<!-- Every slot takes any class value tailwind-merge understands. class merges into root. -->
<Scheduler
bind:events
view="month"
ui={{
root: 'rounded-2xl border border-outline-variant/60',
toolbar: 'bg-surface-container-low',
sidebar: 'bg-surface-container-lowest w-64',
detailPanel: 'w-[28rem]'
}}
/>Scheduler slots
| Slot | Covers |
|---|---|
root | The outer element. class merges here too |
toolbar | The built-in toolbar bar |
body | The row holding the sidebar and the view |
sidebar | The docked sidebar aside |
sidebarScroll | The scroll area inside the docked sidebar |
view | The box the active view fills |
loading | The thin progress bar while source loads |
slideoverOverlay | The backdrop behind any slide-over inside the scheduler |
slideover | The sidebar slide-over below sidebarBreakpoint |
slideoverBody | The body of that slide-over |
detailPanel | The event details and create panels |
App-Wide Defaults
defineSchedulerConfig sets the classes every instance starts from, keyed by component name. Slot classes are appended
before the instance's own class and ui, so
an instance can still override them. A scheduler reads the config when it is created, so the
call has to run before any scheduler renders, and each call replaces the previous config
outright. Put it in a file of its own at src/scheduler.config.ts and import that file once from the root layout. If the app also sets sv5ui defaults with defineConfig, that file sits beside it and is imported the same way; the scheduler does not need it.
// src/scheduler.config.ts
import { defineSchedulerConfig } from '@sv5ui/scheduler';
// Every scheduler created after this call starts from these classes.
// Each call replaces the whole previous config, so keep it in one place.
defineSchedulerConfig({
scheduler: {
slots: { root: 'rounded-2xl border border-outline-variant/60', toolbar: 'px-4' }
},
eventChip: {
slots: { title: 'font-medium' }
},
dateNavigator: { slots: { root: 'gap-2' } },
calendarList: { slots: { title: 'normal-case tracking-normal' } },
searchBox: { slots: { root: 'rounded-full' } },
dragSourceList: { slots: { item: 'rounded-full' } }
});<!-- src/routes/+layout.svelte -->
<script lang="ts">
// Importing the file runs defineSchedulerConfig. The root layout is evaluated
// before any page, so every scheduler in the app is created after it.
import '../scheduler.config';
import './layout.css';
let { children } = $props();
</script>
{@render children()}Configurable components
| Key | Slots and variants |
|---|---|
scheduler | The eleven Scheduler slots above |
eventChip | root, swatch, title, time; variants color, variant, size |
dateNavigator | root, calendar, today |
calendarList | root, title, list, item, label, swatch, name |
searchBox | root, shortcut |
dragSourceList | root, title, list, item, swatch, name, duration |
defaultVariants changes the variant a component uses when none is passed, which matters for an EventChip you render yourself. Inside the calendar each view chooses the chip's size and variant to fit its cells, so a default variant does not reach those chips. Slots do.
// defaultVariants apply to a component you render yourself:
defineSchedulerConfig({ eventChip: { defaultVariants: { variant: 'solid', size: 'sm' } } });
<EventChip {event} /> // now solid and small everywhere you place one
// Inside the calendar each view picks the size and variant that fit its
// cells (soft blocks in the time grid, solid pills in the all-day row and
// the month), so a default variant does not reach those chips. Use slots
// for anything that should change inside the calendar.Slots of the Building Blocks
The five standalone components take the same ui prop and the same config keys.
EventChip
| Slot | Covers |
|---|---|
root | The chip button |
swatch | The colour bar on a soft chip |
title | The title line |
time | The time line, or the leading time of a pill |
DateNavigator
| Slot | Covers |
|---|---|
root | The section |
calendar | The sv5ui Calendar inside |
today | The Today link |
CalendarList
| Slot | Covers |
|---|---|
root | The section |
title | The heading |
list | The ul |
item | One li |
label | The label wrapping checkbox, swatch and name |
swatch | The colour square |
name | The calendar title |
SearchBox
| Slot | Covers |
|---|---|
root | The input |
shortcut | The Kbd hint |
DragSourceList
| Slot | Covers |
|---|---|
root | The section |
title | The heading |
list | The ul |
item | One draggable button |
swatch | The colour square |
name | The item title |
duration | The duration text |
Styling the Inside of the Grid
The grids have no ui slots of their own: their parts are many, and their classes change between releases. What they
have is a set of data-sch-* attributes that do not change, listed below. Target those from CSS for anything a snippet is too
much for: the current-time line, the shading outside business hours, the cell of the selected
date. Svelte scopes styles to the component that declares them, so use :global under a wrapper class. Content is better replaced with the rendering snippets.
Oct 19 – 25, 2026
<!-- The grid inside has no ui slots. Its parts carry data-sch-* attributes
instead, which are a stable contract for CSS. Svelte scopes styles to the
component, so reach into the scheduler with :global. -->
<div class="planner">
<Scheduler bind:events />
</div>
<style>
.planner :global([data-sch-now]) { --color-error: oklch(0.6 0.2 30); }
.planner :global([data-sch-off-hours]) {
background: repeating-linear-gradient(135deg, transparent 0 6px,
color-mix(in oklab, var(--color-outline-variant) 60%, transparent) 6px 7px);
}
.planner :global([data-sch-day-cell][data-sch-anchor]) { outline: 2px solid var(--color-primary); }
.planner :global([data-sch-event-id] ) { border-radius: 2px; }
</style>The DOM contract
Every data-sch-* attribute the scheduler renders. They are public: CSS, tests and interaction plugins may depend on them.
| Attribute | Where, and the value when it has one |
|---|---|
data-sch-scheduler | The root element |
data-sch-view | The root; its value is the active view name |
data-sch-compact | The toolbar while the compact layout is active |
data-sch-toolbar | The built-in toolbar |
data-sch-sidebar | The docked sidebar, or the slide-over body |
data-sch-default-sidebar | The built-in sidebar content |
data-sch-time-grid | The week and day view |
data-sch-month-grid | The month view |
data-sch-year-grid | The year view |
data-sch-agenda | The agenda view |
data-sch-gesture | A grid while a gesture runs; value create, move or resize |
data-sch-drop-target | The grid element that accepts dragSource drops |
data-sch-day | A day column, cell or heading; value YYYY-MM-DD |
data-sch-day-index | A drop cell; the index of its day. Hit testing reads it |
data-sch-all-day | A whole-day drop cell |
data-sch-day-cell | A month cell: a timed event dropped here keeps its time |
data-sch-day-title | The day view title |
data-sch-anchor | The cell or header of the date the scheduler points at |
data-sch-focus | The keyboard focus ring, or the focused month cell |
data-sch-holiday | A year cell on a holiday |
data-sch-holiday-title | The holiday name in a header, cell or agenda heading |
data-sch-busy | A year cell that has events |
data-sch-month | A month section of the year view; value 1 to 12 |
data-sch-week-number | A week number; value is the ISO week |
data-sch-zone | The time zone label |
data-sch-now | The current time line |
data-sch-off-hours | A shaded block outside business hours |
data-sch-empty | The empty state of the week, day or agenda view |
data-sch-event | The box around one event segment; value is the event id |
data-sch-event-id | The default chip or agenda row; value is the event id |
data-sch-event-trigger | The box around a custom event snippet that takes the click |
data-sch-ghost | The preview drawn while dragging |
data-sch-background | A background event block; value is the event id |
data-sch-more | The +N button of a month cell; value is the date |
data-sch-more-list | The list the +N button opens |
data-sch-detail | The event popover card; value is the event id |
data-sch-detail-calendar | The calendar row inside the details |
data-sch-detail-open | The Open details button in the popover |
data-sch-event-panel | The body of the event slide-over; value is the event id |
data-sch-create-panel | The body of the create panel |
data-sch-date-navigator | DateNavigator |
data-sch-search-box | SearchBox |
data-sch-calendar-list | CalendarList |
data-sch-calendar | One CalendarList row; value is the calendar id |
data-sch-drag-source-list | DragSourceList |
Dark Mode
Nothing to do. Tokens resolve to their dark values under the .dark class sv5ui's ModeWatcher sets, and the scheduler has no colour outside the token set. Chips, the shaded off-hours, the
today column and the drag ghost all switch with the page.
Density
There is no density prop. The time grid's height comes from slotHeight and slotMinutes, the toolbar's from its slot, and the month row from the height you give the scheduler.
<!-- There is no density prop; three knobs cover it -->
<Scheduler
bind:events
slotHeight={16} <!-- 24 by default: a tighter time grid -->
slotMinutes={15} <!-- more slots per hour -->
ui={{ toolbar: 'py-1 px-2' }} <!-- a slimmer toolbar -->
/>Props
The two props that restyle one instance; defineSchedulerConfig restyles them all.
| Prop | Type | Default |
|---|---|---|
ui | Partial<Record<SchedulerSlots, ClassNameValue>> | - |
class | ClassNameValue | - |