Customization

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

Tuesday

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

RoleSoft (time grid)
primarybg-primary-container text-on-primary-container
secondarybg-secondary-container text-on-secondary-container
tertiarybg-tertiary-container text-on-tertiary-container
successbg-success-container text-on-success-container
warningbg-warning-container text-on-warning-container
errorbg-error-container text-on-error-container
infobg-info-container text-on-info-container
surfacebg-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

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
<!-- 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

SlotCovers
rootThe outer element. class merges here too
toolbarThe built-in toolbar bar
bodyThe row holding the sidebar and the view
sidebarThe docked sidebar aside
sidebarScrollThe scroll area inside the docked sidebar
viewThe box the active view fills
loadingThe thin progress bar while source loads
slideoverOverlayThe backdrop behind any slide-over inside the scheduler
slideoverThe sidebar slide-over below sidebarBreakpoint
slideoverBodyThe body of that slide-over
detailPanelThe 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
// 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
<!-- 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

KeySlots and variants
schedulerThe eleven Scheduler slots above
eventChiproot, swatch, title, time; variants color, variant, size
dateNavigatorroot, calendar, today
calendarListroot, title, list, item, label, swatch, name
searchBoxroot, shortcut
dragSourceListroot, 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

SlotCovers
rootThe chip button
swatchThe colour bar on a soft chip
titleThe title line
timeThe time line, or the leading time of a pill

DateNavigator

SlotCovers
rootThe section
calendarThe sv5ui Calendar inside
todayThe Today link

CalendarList

SlotCovers
rootThe section
titleThe heading
listThe ul
itemOne li
labelThe label wrapping checkbox, swatch and name
swatchThe colour square
nameThe calendar title

SearchBox

SlotCovers
rootThe input
shortcutThe Kbd hint

DragSourceList

SlotCovers
rootThe section
titleThe heading
listThe ul
itemOne draggable button
swatchThe colour square
nameThe item title
durationThe 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

GMT+7
Mon 19
Tue 20
Wed 21
Thu 22
Fri 23
Sat 24
Sun 25
All day
Lunch break
<!-- 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.

AttributeWhere, and the value when it has one
data-sch-schedulerThe root element
data-sch-viewThe root; its value is the active view name
data-sch-compactThe toolbar while the compact layout is active
data-sch-toolbarThe built-in toolbar
data-sch-sidebarThe docked sidebar, or the slide-over body
data-sch-default-sidebarThe built-in sidebar content
data-sch-time-gridThe week and day view
data-sch-month-gridThe month view
data-sch-year-gridThe year view
data-sch-agendaThe agenda view
data-sch-gestureA grid while a gesture runs; value create, move or resize
data-sch-drop-targetThe grid element that accepts dragSource drops
data-sch-dayA day column, cell or heading; value YYYY-MM-DD
data-sch-day-indexA drop cell; the index of its day. Hit testing reads it
data-sch-all-dayA whole-day drop cell
data-sch-day-cellA month cell: a timed event dropped here keeps its time
data-sch-day-titleThe day view title
data-sch-anchorThe cell or header of the date the scheduler points at
data-sch-focusThe keyboard focus ring, or the focused month cell
data-sch-holidayA year cell on a holiday
data-sch-holiday-titleThe holiday name in a header, cell or agenda heading
data-sch-busyA year cell that has events
data-sch-monthA month section of the year view; value 1 to 12
data-sch-week-numberA week number; value is the ISO week
data-sch-zoneThe time zone label
data-sch-nowThe current time line
data-sch-off-hoursA shaded block outside business hours
data-sch-emptyThe empty state of the week, day or agenda view
data-sch-eventThe box around one event segment; value is the event id
data-sch-event-idThe default chip or agenda row; value is the event id
data-sch-event-triggerThe box around a custom event snippet that takes the click
data-sch-ghostThe preview drawn while dragging
data-sch-backgroundA background event block; value is the event id
data-sch-moreThe +N button of a month cell; value is the date
data-sch-more-listThe list the +N button opens
data-sch-detailThe event popover card; value is the event id
data-sch-detail-calendarThe calendar row inside the details
data-sch-detail-openThe Open details button in the popover
data-sch-event-panelThe body of the event slide-over; value is the event id
data-sch-create-panelThe body of the create panel
data-sch-date-navigatorDateNavigator
data-sch-search-boxSearchBox
data-sch-calendar-listCalendarList
data-sch-calendarOne CalendarList row; value is the calendar id
data-sch-drag-source-listDragSourceList

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.

PropTypeDefault
uiPartial<Record<SchedulerSlots, ClassNameValue>>-
classClassNameValue-