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

  1. The core declares the slot. Certain UI areas are wrapped in a SchedulerContentSlot with a name and typed arguments.
  2. 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

SlotReplacesArguments
resourceToolbarThe entire scheduler toolbar.SchedulerToolbarSlotArgs
eventContextMenuThe right-click menu on an event.SchedulerEventContextMenuSlotArgs
gridContextMenuThe 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

PropertyTypeDescription
currentDateTemporalDateThe currently displayed date.
timelineModeSchedulerTimelineModeThe active view ('day', 'week', …).
viewsSchedulerTimelineView[]All registered views, with labels.
localestringThe active locale.
onNavigate(direction: 'previous' | 'next' | 'today') => voidMoves the visible range.
onModeChange(mode: SchedulerTimelineMode) => voidSwitches view.
onSearchToggle() => voidOpens 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.

💡 Right-click on an event or a grid cell to see the custom context menu.

SchedulerEventContextMenuSlotArgs

PropertyTypeDescription
eventSchedulerEventThe event that was right-clicked.
onClose() => voidDismisses the menu.

SchedulerGridContextMenuSlotArgs

PropertyTypeDescription
clientXnumberClient X coordinate of the right-click.
clientYnumberClient Y coordinate of the right-click.
onClose() => voidDismisses 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:

PropertyTypeDescription
eventSchedulerEventThe event being viewed or edited.
isOpenbooleanDialog visibility. Dialog renderer only.
isAllDaybooleanWhether the event is all-day.
onClose() => voidCloses the panel or dialog.
onEventUpdate(event: SchedulerEvent) => voidCommits an update.
onEventDelete(eventId: string) => voidDeletes the event.

On this page