Content Slots
The scheduler renders through a Preact engine, but specific regions are marked as slots. When you provide a renderer for one, the built-in content is hidden and your framework component is portaled into that exact position in the DOM, so it participates in your app's context, state, and styling rather than living in an isolated island.
How It Works
- The core declares the slot. Certain UI areas are wrapped in a
SchedulerContentSlotwith a name and typed arguments. - You provide the implementation. The adapter subscribes to slot registrations and mounts your component into the placeholder element, passing the slot's arguments through.
Available Slots
| Slot | Replaces | Arguments |
|---|---|---|
resourceToolbar | The entire scheduler toolbar. | SchedulerToolbarSlotArgs |
eventContextMenu | The right-click menu on an event. | SchedulerEventContextMenuSlotArgs |
gridContextMenu | The right-click menu on a grid cell. | SchedulerGridContextMenuSlotArgs |
Event detail UI is customized through two dedicated props rather than the slot map; see Event Detail Renderers.
Providing a Slot
Each adapter exposes slots idiomatically:
<DayflowScheduler
app={app}
slots={{
resourceToolbar: ({ currentDate, onNavigate }) => (
<MyToolbar date={currentDate} onNavigate={onNavigate} />
),
}}
/><DayflowScheduler :app="app">
<template #resourceToolbar="{ currentDate, onNavigate }">
<MyToolbar :date="currentDate" @navigate="onNavigate" />
</template>
</DayflowScheduler><dayflow-scheduler [app]="app">
<ng-template dfSlot="resourceToolbar"
let-currentDate="currentDate"
let-onNavigate="onNavigate">
<my-toolbar [date]="currentDate" (navigate)="onNavigate($event)" />
</ng-template>
</dayflow-scheduler><script lang="ts">
import type { SchedulerSlots } from '@dayflow-scheduler/svelte';
const slots: SchedulerSlots = { resourceToolbar: resourceToolbarSnippet };
</script>
<DayflowScheduler {app} {slots} />
{#snippet resourceToolbarSnippet({ currentDate, onNavigate })}
<MyToolbar date={currentDate} {onNavigate} />
{/snippet}Keep renderers stable
In React, wrap slot renderers in useCallback. Recreating the function on
every render re-registers the slot and remounts your component.
Custom Toolbar
resourceToolbar replaces navigation, the date label, the view switcher, and search with your own layout.
SchedulerToolbarSlotArgs
| Property | Type | Description |
|---|---|---|
currentDate | TemporalDate | The currently displayed date. |
timelineMode | SchedulerTimelineMode | The active view ('day', 'week', …). |
views | SchedulerTimelineView[] | All registered views, with labels. |
locale | string | The active locale. |
onNavigate | (direction: 'previous' | 'next' | 'today') => void | Moves the visible range. |
onModeChange | (mode: SchedulerTimelineMode) => void | Switches view. |
onSearchToggle | () => void | Opens or closes the search drawer. |
View source code
import { DayflowScheduler, useSchedulerApp } from '@dayflow-scheduler/react';
import type { SchedulerToolbarSlotArgs } from '@dayflow-scheduler/react';
import { useCallback } from 'react';
function CustomToolbar({
currentDate,
timelineMode,
views,
onNavigate,
onModeChange,
}: SchedulerToolbarSlotArgs) {
const label = currentDate.toLocaleString('en-US', {
month: 'long',
year: 'numeric',
});
return (
<div className='flex items-center justify-between border-b px-4 py-2.5'>
<div className='flex items-center gap-2'>
<button onClick={() => onNavigate('previous')}>‹</button>
<span>{label}</span>
<button onClick={() => onNavigate('next')}>›</button>
<button onClick={() => onNavigate('today')}>Today</button>
</div>
<div className='flex gap-1'>
{views.map(view => (
<button
key={view.type}
className={timelineMode === view.type ? 'active' : ''}
onClick={() => onModeChange(view.type)}
>
{view.label ?? view.type}
</button>
))}
</div>
</div>
);
}
export function MyScheduler() {
const app = useSchedulerApp({ resources, events, views });
const resourceToolbar = useCallback(
(args: SchedulerToolbarSlotArgs) => <CustomToolbar {...args} />,
[]
);
return <DayflowScheduler app={app} slots={{ resourceToolbar }} />;
}Custom Context Menus
eventContextMenu and gridContextMenu replace the built-in right-click menus. The scheduler still handles positioning and dismissal; your component only renders the contents and calls onClose.
SchedulerEventContextMenuSlotArgs
| Property | Type | Description |
|---|---|---|
event | SchedulerEvent | The event that was right-clicked. |
onClose | () => void | Dismisses the menu. |
SchedulerGridContextMenuSlotArgs
| Property | Type | Description |
|---|---|---|
clientX | number | Client X coordinate of the right-click. |
clientY | number | Client Y coordinate of the right-click. |
onClose | () => void | Dismisses the menu. |
View source code
import { DayflowScheduler, useSchedulerApp } from '@dayflow-scheduler/react';
import type {
SchedulerEventContextMenuSlotArgs,
SchedulerGridContextMenuSlotArgs,
} from '@dayflow-scheduler/react';
import { useCallback } from 'react';
function EventContextMenu({
event,
onClose,
}: SchedulerEventContextMenuSlotArgs) {
return (
<div className='min-w-[160px] p-1'>
<button
onClick={() => {
duplicate(event);
onClose();
}}
>
Duplicate
</button>
<button
onClick={() => {
remove(event.id);
onClose();
}}
>
Delete
</button>
</div>
);
}
function GridContextMenu({ onClose }: SchedulerGridContextMenuSlotArgs) {
return (
<div className='min-w-[160px] p-1'>
<button
onClick={() => {
createEvent();
onClose();
}}
>
New event
</button>
</div>
);
}
export function MyScheduler() {
const app = useSchedulerApp({ resources, events, views });
const eventContextMenu = useCallback(
(args: SchedulerEventContextMenuSlotArgs) => <EventContextMenu {...args} />,
[]
);
const gridContextMenu = useCallback(
(args: SchedulerGridContextMenuSlotArgs) => <GridContextMenu {...args} />,
[]
);
return (
<DayflowScheduler app={app} slots={{ eventContextMenu, gridContextMenu }} />
);
}Event Detail Renderers
Event detail UI is customized with two top-level props on DayflowScheduler, not through the slots map.
customDetailPanelContent
Replaces the body of the floating detail panel, keeping its positioning and chrome:
<DayflowScheduler
app={app}
customDetailPanelContent={({ event, onClose, onEventDelete }) => (
<div className='p-4'>
<h3>{event.title}</h3>
<button onClick={() => onEventDelete(event.id)}>Delete</button>
<button onClick={onClose}>Close</button>
</div>
)}
/>customEventDetailDialog
Replaces the entire modal dialog. Requires useEventDetailDialog: true on the app config:
<DayflowScheduler
app={app}
customEventDetailDialog={({ event, isOpen, onClose }) => (
<MyModal isOpen={isOpen} onClose={onClose}>
<EventForm event={event} />
</MyModal>
)}
/>Both renderers receive the same arguments:
| Property | Type | Description |
|---|---|---|
event | SchedulerEvent | The event being viewed or edited. |
isOpen | boolean | Dialog visibility. Dialog renderer only. |
isAllDay | boolean | Whether the event is all-day. |
onClose | () => void | Closes the panel or dialog. |
onEventUpdate | (event: SchedulerEvent) => void | Commits an update. |
onEventDelete | (eventId: string) => void | Deletes the event. |
Related Documentation
- DayflowScheduler: every prop, including
sidebarrenderers. - Views: the built-in switcher a custom toolbar replaces.
- Theme Customization: theme-aware classes for slot markup.