The React Scheduler displays bookings, shifts, and tasks on a horizontal resource timeline. Connect it to React state, callbacks, and custom JSX content.

The component is available in DayPilot Lite (open-source, Apache License 2.0) and DayPilot Pro (commercial). Both include React integration and TypeScript declarations.

React Scheduler Component (DayPilot)

React Scheduler Features

DayPilot Lite and Pro

  • Resources and time on one grid. Each row represents a resource, and each event identifies the row where it belongs. Use the time scale and time headers to show hours, days, or longer periods. The introductory example below displays two rooms with multi-day reservations.

  • Selection, moving, and resizing. Select an empty range to create an event, drag an event to another date or resource, or resize it to change its duration. The component supplies the selected dates and resource to your callback. Your application chooses the editor and saves the result; the examples below show these steps separately.

  • React content inside the schedule. Render JSX in events, cells, row headers, time headers, and the corner. The row-header tutorial compares an HTML string, an active-area icon that opens a dialog, and a separate React component receiving the current row. These are different ways to customize the same header, depending on the content and interaction you need.

  • Date navigation and rendering while scrolling. Bind resources and events to React state, change the displayed period, and load the matching records. Progressive event rendering limits the event elements drawn around the viewport. Loading fewer records from a backend is a separate decision.

Additional DayPilot Pro Features

  • Resource trees and additional columns. Display an expandable resource hierarchy and add row-header columns for resource fields. The resource-management example also shows controls for adding, renaming, moving, and deleting resource rows.

  • Links with application-specific meaning. Connect events using links, then change their color, line style, text, or context menu. The React task-link example compares the connected event dates and highlights a finish-to-start link when the next task begins before its predecessor ends. The callback performs that check; drawing a link does not automatically reschedule either event.

  • Alternative dates beside an event. Event versions display additional read-only bars above or below the main event. They can show an earlier schedule while the main event remains editable. See the planned-versus-actual example below.

  • Feedback during dragging. Moving validation can reject a proposed position and display a reason before the user drops the event. Your callback supplies the rule and message.

  • Loading and export. Dynamic event loading requests events for the viewport while scrolling. Export the schedule as an image or Excel spreadsheet. The PDF example combines Scheduler export with a PDF library to split the schedule across pages.

See the feature matrix for a detailed edition comparison. For time running vertically, with days or resources as columns, use the React Calendar.

Keep a Planned Version Visible While Dates Change

hero

The Next.js planned-versus-actual example uses the React Scheduler's event versions to display the planned dates above an editable task. Switch to tracking mode, then move or resize the main task: the extra bar keeps the planned dates visible, making an earlier start or longer duration easy to see. Try the live demo (Pro).

A version is supplied through an event's versions array. It stays in the same resource row as the main event and is read-only. The sample's React state and mode switch decide which records form the plan and which are editable; saving a baseline or recording changes is application logic. The same display mechanism can show other supplied date ranges for comparison.

Demos and Tutorials

Each tutorial includes a downloadable project. The edition used by the example is shown beside its link.

Getting Started

Custom Content and Navigation

Planning Features

Backend Applications and Additional Recipes

Configure a Starter Project

Use Scheduler UI Builder to explore settings in a live preview and download a React starter. Select the edition that matches your application. Continue adjusting the generated configuration in your own code.

Installation

DayPilot Lite

Install @daypilot/daypilot-lite-react from npm:

npm install @daypilot/daypilot-lite-react

DayPilot Pro

At npm.daypilot.org, select React and copy the installation command for daypilot-pro-react. Licensed customers can obtain their package URL in the customer area.

The examples use JavaScript/JSX and function components with React 18 or later.

Display Room Reservations

Two rooms appear as rows. Each cell is one day, so the multi-day bookings are easy to see.

Scheduler.jsx

import {useState} from 'react';
import {DayPilot, DayPilotScheduler} from '@daypilot/daypilot-lite-react';

export default function Scheduler() {
  const [events] = useState([
    {id: 1, text: 'Reservation A', resource: 'R1',
      start: '2026-10-05', end: '2026-10-08'},
    {id: 2, text: 'Reservation B', resource: 'R2',
      start: '2026-10-20', end: '2026-10-23'}
  ]);

  const [resources] = useState([{id: 'R1', name: 'Room 1'}, {id: 'R2', name: 'Room 2'}]);

  return <DayPilotScheduler
    startDate="2026-10-01"
    days={31}
    scale="Day"
    timeHeaders={[{groupBy: 'Month'}, {groupBy: 'Day', format: 'd'}]}
    events={events}
    resources={resources}
  />;
}

The resource on each event matches a resource ID. startDate, days and scale define the timeline; timeHeaders label its month and days. Reservation A occupies October 5–7 because its end is exclusive.

The examples use Lite. For Pro, change the package import to daypilot-pro-react.

See event loading and resource loading for data formats and loading options.

Navigate and Load Reservations

Previous and Next change the month and load its reservations. The endpoint returns {resources: [...], events: [...]}; keeping both collections in the same response avoids showing bookings against an unrelated resource list.

SchedulerLoading.jsx

import {useState, useEffect} from 'react';
import {DayPilot, DayPilotScheduler} from '@daypilot/daypilot-lite-react';

export default function Scheduler() {
  const [startDate, setStartDate] = useState(new DayPilot.Date('2026-10-01'));

  const [events, setEvents] = useState([]);

  const [resources, setResources] = useState([]);

  useEffect(() => {
    let active = true;
    const start = startDate;
    const end = start.addMonths(1);
    const url = `/api/schedule?start=${start}&end=${end}`;
    async function load() {
      try {
        const {data} = await DayPilot.Http.get(url);
        if (!active) { return; }
        setResources(data.resources);
        setEvents(data.events);
      } catch {
        if (active) { await DayPilot.Modal.alert('Could not load data.'); }
      }
    }
    load();
    return () => { active = false; };
  }, [startDate]);

  function navigate(offset) {
    setStartDate(date => date.addMonths(offset));
  }

  return <>
    <button onClick={() => navigate(-1)}>Previous month</button>
    <button onClick={() => navigate(1)}>Next month</button>
    <DayPilotScheduler
      startDate={startDate}
      days={startDate.daysInMonth()}
      scale="Day"
      timeHeaders={[{groupBy: 'Month'}, {groupBy: 'Day', format: 'd'}]}
      events={events}
      resources={resources}
    />
  </>;
}

The /api/schedule URL represents your own backend. Return items that overlap the requested range: item start is before the requested end, and item end is after the requested start. Failed requests display a DayPilot dialog.

The effect cleanup prevents an older response from replacing data after another navigation action.

For large schedules, Pro also supports dynamic event loading as the user scrolls. That is separate from replacing the events for a new month.

Create Reservations by Selecting Time

This complete variation of the first example handles time range selection. The selection supplies the dates and resource; the dialog supplies the name.

SchedulerCreation.jsx

import {useState} from 'react';
import {DayPilot, DayPilotScheduler} from '@daypilot/daypilot-lite-react';

export default function Scheduler() {
  const [events] = useState([
    {id: 1, text: 'Reservation A', resource: 'R1',
      start: '2026-10-05', end: '2026-10-08'},
    {id: 2, text: 'Reservation B', resource: 'R2',
      start: '2026-10-20', end: '2026-10-23'}
  ]);

  const [resources] = useState([{id: 'R1', name: 'Room 1'}, {id: 'R2', name: 'Room 2'}]);

  return <DayPilotScheduler
    startDate="2026-10-01"
    days={31}
    scale="Day"
    timeHeaders={[{groupBy: 'Month'}, {groupBy: 'Day', format: 'd'}]}
    events={events}
    resources={resources}
    onTimeRangeSelected={async args => {
      const modal = await DayPilot.Modal.prompt('Reservation name:', 'New reservation');
      args.control.clearSelection();
      if (modal.canceled) { return; }
      args.control.events.add({
        id: DayPilot.guid(), text: modal.result,
        start: args.start, end: args.end, resource: args.resource
      });
    }}
  />;
}

Canceling the dialog leaves the data unchanged. events.add() inserts the new event locally. In an application with a backend, create the record with a POST request and use the returned ID before adding it to the view.

Move, Resize and Save Events

Moving changes the event position; resizing changes its duration. Both work in the first example without extra configuration. The component updates the display and the supplied data object automatically.

Use the callbacks after the edit to persist the result. This example sends the changed record to the backend; it does not reload the collection after each drag.

SchedulerSaving.jsx

import {useState} from 'react';
import {DayPilot, DayPilotScheduler} from '@daypilot/daypilot-lite-react';

export default function Scheduler() {
  const [events] = useState([
    {id: 1, text: 'Reservation A', resource: 'R1',
      start: '2026-10-05', end: '2026-10-08'},
    {id: 2, text: 'Reservation B', resource: 'R2',
      start: '2026-10-20', end: '2026-10-23'}
  ]);

  const [resources] = useState([{id: 'R1', name: 'Room 1'}, {id: 'R2', name: 'Room 2'}]);

  async function saveEvent(data) {
    try {
      await DayPilot.Http.put(`/api/events/${data.id}`, data);
    } catch {
      await DayPilot.Modal.alert('The change could not be saved.');
    }
  }

  return <DayPilotScheduler
    startDate="2026-10-01"
    days={31}
    scale="Day"
    timeHeaders={[{groupBy: 'Month'}, {groupBy: 'Day', format: 'd'}]}
    events={events}
    resources={resources}
    onEventMoved={args => saveEvent(args.e.data)}
    onEventResized={args => saveEvent(args.e.data)}
  />;
}

The /api/events/{id} endpoint accepts the updated JSON record. A failed save is reported while the local edit remains visible; add retry or rollback behavior to suit your application. To disable an interaction, set eventMoveHandling or eventResizeHandling to "Disabled".

Handle an Event Click

To display the clicked item’s name, add this prop to the existing component:

onEventClick={args => DayPilot.Modal.alert(DayPilot.Util.escapeHtml(args.e.text()))}

DayPilot.Modal.alert() accepts HTML. escapeHtml() displays the item’s name as plain text.

Render Custom React Content

This independent variation of the first example adds JSX to the event through onBeforeEventDomAdd.

SchedulerContent.jsx

import {useState} from 'react';
import {DayPilot, DayPilotScheduler} from '@daypilot/daypilot-lite-react';

export default function Scheduler() {
  const [events] = useState([
    {id: 1, text: 'Reservation A', resource: 'R1',
      start: '2026-10-05', end: '2026-10-08'},
    {id: 2, text: 'Reservation B', resource: 'R2',
      start: '2026-10-20', end: '2026-10-23'}
  ]);

  const [resources] = useState([{id: 'R1', name: 'Room 1'}, {id: 'R2', name: 'Room 2'}]);

  return <DayPilotScheduler
    startDate="2026-10-01"
    days={31}
    scale="Day"
    timeHeaders={[{groupBy: 'Month'}, {groupBy: 'Day', format: 'd'}]}
    events={events}
    resources={resources}
    onBeforeEventDomAdd={args => {
      args.element = <strong>{args.e.text()}</strong>;
    }}
  />;
}

Access the Scheduler API

Find Reservation B by ID and scroll the timeline to its start. This is useful when a booking or task lies outside the current horizontal viewport.

SchedulerApi.jsx

import {useState} from 'react';
import {DayPilot, DayPilotScheduler} from '@daypilot/daypilot-lite-react';

export default function Scheduler() {
  const [scheduler, setScheduler] = useState(null);

  const [events] = useState([
    {id: 1, text: 'Reservation A', resource: 'R1',
      start: '2026-10-05', end: '2026-10-08'},
    {id: 2, text: 'Reservation B', resource: 'R2',
      start: '2026-10-20', end: '2026-10-23'}
  ]);

  const [resources] = useState([{id: 'R1', name: 'Room 1'}, {id: 'R2', name: 'Room 2'}]);

  function showReservation() {
    const control = scheduler;
    const event = control.events.find(2);
    control.scrollTo(event.start());
  }

  return <>
    <button disabled={!scheduler} onClick={showReservation}>Show reservation B</button>
    <DayPilotScheduler
      startDate="2026-10-01"
      days={31}
      scale="Day"
      timeHeaders={[{groupBy: 'Month'}, {groupBy: 'Day', format: 'd'}]}
      events={events}
      resources={resources}
      controlRef={setScheduler}
    />
  </>;
}

React receives the underlying control through controlRef. The wrapper creates and disposes it. See the DayPilot.Scheduler API for methods, properties and callbacks.

Use Next.js

Place the introductory component in Scheduler.jsx and add 'use client'; at the top. Create a client wrapper that loads it with server rendering disabled:

SchedulerClient.jsx

'use client';

import dynamic from 'next/dynamic';

const Scheduler = dynamic(() => import('./Scheduler'), {ssr: false});

export default function SchedulerClient() {
  return <Scheduler />;
}

Import SchedulerClient in your page and render <SchedulerClient />. The Next.js client wrapper keeps the DayPilot component in the browser. The tutorials above provide complete project setups.

Availability

Availability of this feature in DayPilot editions:

ProductLitePro
DayPilot for JavaScript
DayPilot for ASP.NET WebForms
DayPilot for ASP.NET MVC
DayPilot for Java