Sidebar
A panel beside the grid, or over it on a narrow screen. Turn on the built-in one with a single prop, add to it, or replace it with your own.
The Built-in Sidebar
sidebar alone turns it on. It shows four parts, two of them only when the matching prop is given, and
takes a header and a footer. The demo docks at
768px because the docs column is narrower than the default breakpoint;
see the next section.
Parts
| Part | What it does |
|---|---|
DateNavigator | A month calendar. A dot under every day that has events after the filters; picking a day navigates to it |
SearchBox | Bound to search. The / key focuses it from anywhere on the page |
CalendarList | One checkbox per calendar, bound to hiddenCalendars. Only when calendars is given |
DragSourceList | The backlog of dragSources, ready to drop on the grid. Only when dragSources is given |
Oct 19 – 25, 2026
<!-- sidebar alone turns on the built-in one: a date navigator, a search box,
the calendar list when there are calendars, the backlog when there are dragSources -->
<Scheduler
bind:events
bind:sidebarOpen
sidebar
sidebarSide="start"
{calendars}
{dragSources}
>
{#snippet sidebarHeader()}
<p class="text-sm font-semibold">Team calendar</p>
{/snippet}
{#snippet sidebarFooter()}
<p class="text-xs text-on-surface/60">Synced a moment ago</p>
{/snippet}
</Scheduler>Docked or Slide-over
The sidebar docks beside the grid when the scheduler is at least sidebarBreakpoint pixels wide, 1024 by default, and becomes a slide-over inside the calendar below that. The menu
button in the toolbar opens the slide-over; picking a date in it closes it. sidebarOpen controls only the docked state. The toggle below moves the breakpoint above and below the demo's
width, so you can see both without resizing the window; before the scheduler has measured itself,
on the server, it counts as docked.
Oct 19 – 25, 2026
<!-- The sidebar docks beside the grid when the scheduler is at least
sidebarBreakpoint pixels wide (1024 by default), and becomes a slide-over
inside the calendar below that. Picking a date in the slide-over closes it.
sidebarOpen only controls the docked state. -->
<Scheduler bind:events sidebar sidebarBreakpoint={768} />
<!-- Always a slide-over, whatever the width -->
<Scheduler bind:events sidebar sidebarBreakpoint={Infinity} />Side
sidebarSide is start or end,
relative to the reading direction: start is the left in LTR and the right under dir="rtl". The slide-over comes in from the same side.
<!-- start is the left in LTR and the right in RTL; end is the other side -->
<Scheduler bind:events sidebar sidebarSide="end" />Adding Content
sidebarHeader and sidebarFooter render above and below the built-in parts and receive the same arguments a custom sidebar does.
The demo's header opens the create panel for 9:00 on the anchor day through draft;
the footer counts what is left after the filters.
Oct 19 – 25, 2026
<script lang="ts">
import { now } from '@internationalized/date';
import { Scheduler, type SidebarSnippetProps, type SlotSelection } from '@sv5ui/scheduler';
let draft = $state<SlotSelection | null>(null);
</script>
<!-- Both receive SidebarSnippetProps. The header here opens the create panel
through draft; the footer reads the filtered store. -->
<Scheduler bind:events bind:draft sidebar createPanel={form}>
{#snippet sidebarHeader({ date }: SidebarSnippetProps)}
<Button
label="New event"
icon="lucide:plus"
block
onclick={() => {
const start = date.set({ hour: 9, minute: 0 });
draft = { start, end: start.add({ hours: 1 }), allDay: false };
}}
/>
{/snippet}
{#snippet sidebarFooter({ events }: SidebarSnippetProps)}
<p class="text-xs text-on-surface/60">{events.length} events after filters</p>
{/snippet}
</Scheduler>A Sidebar of Your Own
Pass a snippet as sidebar and it replaces the whole panel, docked and slide-over alike. It receives the anchor date, the
range, the filtered store and a navigate function. docked tells you whether to show a close button, and events lists series as one entry each, not expanded, so the demo leaves them out of "Up next".
Oct 19 – 25, 2026
<!-- A snippet as sidebar replaces the whole panel. events is the filtered store
with series not expanded, so filter recurrence out when listing what is next. -->
<Scheduler bind:events sidebar={upNext} />
{#snippet upNext({ date, events, navigate, docked, close }: SidebarSnippetProps)}
<div class="flex h-full flex-col gap-4 p-4">
<div class="flex items-center justify-between">
<h3 class="font-semibold">Up next</h3>
{#if !docked}
<Button icon="lucide:x" variant="ghost" size="xs" aria-label="Close" onclick={close} />
{/if}
</div>
<ul class="space-y-2">
{#each events
.filter((e) => !e.recurrence && !e.background && e.end.compare(date) > 0)
.toSorted((a, b) => a.start.compare(b.start))
.slice(0, 6) as event (event.id)}
<li>
<button type="button" class="w-full rounded-xl border p-3 text-start" onclick={() => navigate(event.start, 'day')}>
<p class="text-sm font-medium">{event.title}</p>
<p class="text-xs text-on-surface/60">{describeTime(event)}</p>
</button>
</li>
{/each}
</ul>
</div>
{/snippet}interface SidebarSnippetProps<T> {
date: ZonedDateTime; // the anchor
view: string;
range: DateRange;
events: SchedulerEvent<T>[]; // the whole store after hiddenCalendars, search and filter; series as one entry
scheduler: SchedulerContext;
navigate: (date: ZonedDateTime, view?: string) => void;
close: () => void; // closes the slide-over, or the docked panel (sidebarOpen becomes false)
docked: boolean; // true beside the grid, false as a slide-over
}Filter by Your Own Data
Put the controls in the header and the predicate in filter.
The built-in parts follow: the navigator's dots and the search results only count events
that pass. See Calendars & Filtering for the three filters and their order.
Oct 19 – 25, 2026
<script lang="ts">
interface Meeting { status: 'confirmed' | 'tentative'; mine: boolean }
let status = $state('all');
let onlyMine = $state(false);
</script>
<!-- Controls in the sidebar header, the predicate on the scheduler. The built-in
parts keep working: the date navigator's dots follow the filter too. -->
<Scheduler
bind:events
{calendars}
sidebar
filter={(event) =>
(status === 'all' || event.data?.status === status) && (!onlyMine || event.data?.mine === true)}
>
{#snippet sidebarHeader()}
<ToggleGroup items={statusItems} bind:value={status} size="sm" />
<Switch label="Only mine" bind:checked={onlyMine} />
{/snippet}
</Scheduler>Outside the Scheduler
The four parts are exported on their own. For a layout of your own, a sidebar shared by two calendars, or a search box in the page header, render them where you like and bind them to the same state; see Building Blocks.
Props
The sidebar props. sidebarOpen is bindable.
| Prop | Type | Default |
|---|---|---|
sidebar | boolean | Snippet<[SidebarSnippetProps<T>]> | false |
sidebarHeader | Snippet<[SidebarSnippetProps<T>]> | - |
sidebarFooter | Snippet<[SidebarSnippetProps<T>]> | - |
sidebarOpen | boolean | true |
sidebarSide | 'start' | 'end' | 'start' |
sidebarBreakpoint | number | 1024 |
dragSources | DragSourceData<T>[] | [] |
filter | (event: SchedulerEvent<T>) => boolean | - |
Snippets
What sidebar, sidebarHeader and sidebarFooter receive.
| Type | Fields |
|---|---|
SidebarSnippetProps | date, view, range, events, scheduler, navigate, close, docked |