Overview
Horizontal timeline with support for hour, day, week, month and year views
The React Scheduler component supports drag-and-drop event creation, resizing, and editing
Customizable time headers, cell duration, and timeline length
Support for thousands of rows and events with smooth, progressive rendering
Built-in modal dialogs for editing event details
Flexible event styling: colors, icons, tooltips, and custom CSS
Dynamic theme switching with built-in light/dark themes and custom theme support
API integration for loading resources and events asynchronously
Fully open-source with no external dependencies or license fees
License
Apache License 2.0
Installation
Start by installing the DayPilot Lite for React package:
npm install @daypilot/daypilot-lite-react
Once installed, you can use the <DayPilotScheduler>
component to add the Scheduler to your React application:
import { DayPilotScheduler } from "@daypilot/daypilot-lite-react";
const ReactScheduler = () => {
return (
<div>
<DayPilotScheduler />
</div>
);
};
export default ReactScheduler;
Generate a New React Scheduler Project with the Online Configurator
The Scheduler includes a rich API that allows you to fully customize its appearance and behavior—including drag-and-drop support, time headers, and timeline units.
For a quick start, you can use the UI Builder online tool:
Set the most common properties using a visual interface.
Preview your changes instantly using a live Scheduler component.
View and copy the generated configuration object.
Download a ready-to-run React or Next.js project with the selected settings and all necessary boilerplate.
Here is the React Scheduler component with a basic configuration:
import { DayPilotScheduler } from "@daypilot/daypilot-lite-react";
const ReactScheduler = () => {
const config = {
timeHeaders: [
{ groupBy: "Month" },
{ groupBy: "Day", format: "d" }
],
scale: "Day",
days: DayPilot.Date.today().daysInMonth(),
startDate: DayPilot.Date.today().firstDayOfMonth(),
};
return (
<div>
<DayPilotScheduler {...config} />
</div>
);
};
export default ReactScheduler;
The UI Builder outputs the configuration as a single object, which you can apply using spread syntax ({...config}
).
This is functionally equivalent to writing the properties inline:
import { DayPilotScheduler } from "@daypilot/daypilot-lite-react";
const ReactScheduler = () => {
return (
<div>
<DayPilotScheduler
timeHeaders={[ { groupBy: "Month" }, { groupBy: "Day", format: "d" } ]}
scale={"Day"}
days={DayPilot.Date.today().daysInMonth()}
startDate={DayPilot.Date.today().firstDayOfMonth()}
/>
</div>
);
}
export default ReactScheduler;
Customize the Scheduler Appearance: CSS Themes
You can style the React Scheduler component by applying a CSS theme. A few sample themes are included in the download package.
You can also design a custom theme using the Theme Designer - an online tool with a live preview:
Customize properties like background color, text color, font family and size, border radius, and border color.
Preview the changes in real time.
Download the generated CSS file and include it in your project.
This tutorial includes two themes created with the Theme Designer: Light and Dark. You can switch between them dynamically using a dropdown selector.
Light Theme:
Dark Theme:
Progressive Rendering: Ready for Big Data Sets
The React Scheduler is optimized to handle a large number of rows and events efficiently. It uses progressive rendering to keep the UI responsive and smooth, even with big data sets.
Key performance features:
Only the elements currently visible in the viewport are rendered.
Rows and events are rendered incrementally as the user scrolls.
A small delay is used to debounce rendering during scroll and resize operations.
These rendering delays are fully configurable. You can even set them to 0
to make rendering synchronous and disable debouncing. However, synchronous rendering may reduce performance on low-resource devices or when displaying large datasets.
Timeline Configuration
The timeline is the core of the Scheduler. You can fully control its range, granularity, and visual layout using a few key properties:
1. Set the Time Scale
The scale
property defines the basic time unit of the timeline (e.g., hour, day, week):
<DayPilotScheduler
scale={"Day"}
/>
Common values:
"Hour"
– 1 cell = 1 hour"Day"
– 1 cell = 1 day"Week"
– 1 cell = 1 week
You can also use fine-grained units with cellDuration
, e.g., 15
(for 15-minute slots), when scale
is "Cell"
.
2. Define Time Headers
You can define multiple levels of headers using the timeHeaders
array. This improves readability by showing both coarse and fine time units.
<DayPilotScheduler
scale={"Day"}
timeHeaders={[
{ groupBy: "Month" },
{ groupBy: "Day" }
]}
/>
You can use the format
property to customize the display (e.g., "dddd"
for full day names, "d"
for numeric day). The format follows the rules described in the DayPilot.Data.toString() method docs.
3. Set the Timeline Start
The timeline's starting point is controlled by the startDate
property. To allow dynamic updates, store it in state:
const [startDate, setStartDate] = useState(DayPilot.Date.today().firstDayOfMonth());
<DayPilotScheduler startDate={startDate} />
You can adjust the timeline programmatically by calling setStartDate()
. This is useful for implementing custom navigation buttons.
4. Define the Timeline Length
The days
property determines how many cells to display (typically aligned with the selected scale):
const [days, setDays] = useState(DayPilot.Date.today().daysInMonth());
<DayPilotScheduler days={days} />
For example:
To show one month:
days = 30
(withscale: "Day"
)To show one work week:
days = 5
(starting on Monday)
5. Set the Cell Width
The cellWidth
property controls how wide each time cell is (in pixels). This directly affects horizontal scroll and layout density:
<DayPilotScheduler cellWidth={50} />
Typical values: 30 - 100 pixels
For
"Hour"
or"Cell"
scales, smaller widths keep the timeline compact
Example: Full Timeline Setup
This will render a daily timeline for the current month, with 60px-wide cells and month/day headers.
const [startDate, setStartDate] = useState(DayPilot.Date.today().firstDayOfMonth());
const [days, setDays] = useState(DayPilot.Date.today().daysInMonth());
<DayPilotScheduler
scale="Day"
startDate={startDate}
days={days}
cellWidth={60}
timeHeaders={[
{ groupBy: "Month" },
{ groupBy: "Day", format: "d" }
]}
/>
Load Rows (Resources)
In the Scheduler, each row represents a resource - such as a person, room, machine, or any item you want to schedule.
To define and display these rows, use the resources
prop of the <DayPilotScheduler>
component.
Basic Setup
Use React state to hold the resource list, and populate it in a useEffect()
hook:
const [resources, setResources] = useState([]);
useEffect(() => {
const resources = [
{ name: "Resource 1", id: "R1" },
{ name: "Resource 2", id: "R2" },
{ name: "Resource 3", id: "R3" },
{ name: "Resource 4", id: "R4" },
{ name: "Resource 5", id: "R5" },
{ name: "Resource 6", id: "R6" },
{ name: "Resource 7", id: "R7" },
];
setResources(resources);
}, []);
Pass the resources to the Scheduler:
<DayPilotScheduler resources={resources} />
Resource Object Format
Each resource should be an object with at least these two properties:
{
id: "R1", // Unique resource identifier (string or number)
name: "Room A" // Text displayed in the row header
}
You can include additional fields to support features like styling, tooltips, or custom context menus.
Loading from an API
You can also fetch resource data from a server or REST API:
useEffect(() => {
const loadResources = async () => {
const response = await DayPilot.Http.get("/api/resources");
setResources(response.data);
};
loadResources();
}, []);
Load Events
To display scheduled items on the timeline, you need to provide them via the events
prop of the <DayPilotScheduler>
component.
1. Define Event State
First, define a React state variable to hold your event data:
const [events, setEvents] = useState([]);
2. Load Events (Static Example)
You can load events directly in a useEffect()
hook or fetch them from a backend:
useEffect(() => {
const events = [
{
id: 1,
text: "Event 1",
start: "2025-06-01T00:00:00",
end: "2025-06-05T00:00:00",
resource: "R2",
barColor: "#38761d",
barBackColor: "#93c47d"
}
];
setEvents(events);
}, []);
3. Bind Events to the Scheduler
Pass the loaded events to the Scheduler via the events
prop:
<DayPilotScheduler events={events} />
4. Event Data Format
Each event should include:
{
id: "unique-id", // required
text: "Event title", // required
start: "2025-06-01T00:00:00", // required (ISO 8601)
end: "2025-06-05T00:00:00", // required
resource: "R1", // required: matches row ID
backColor: "#38761d", // optional: background color
}
Use ISO 8601 format (YYYY-MM-DDTHH:mm:ss
) for the date/time values.
For more about the item structure, see DayPilot.Event.data in the API docs.
5. Understanding Start and End Times
The start
and end
values define the exact time bounds of the event. For example, this event:
{
start: "2025-06-01T00:00:00",
end: "2025-06-06T00:00:00"
}
...will span five full days, because the end
value is exclusive. This means it ends at the start of June 6 (00:00:00), not during that day.
6. Adjusting End Time Interpretation (Optional)
When using scale: "Day"
, you may want to treat the end
date as inclusive - so that an event ending on "2025-06-05"
visually includes that full day.
To support this, use the eventEndSpec
property:
<DayPilotScheduler eventEndSpec="Date" />
This setting tells the Scheduler to treat dates without a time portion (e.g., "2025-06-05"
) as the end of the day, not the beginning.
7. Loading Events from an API (Optional)
You can also load events from a backend using DayPilot.Http.get()
:
useEffect(() => {
const loadEvents = async () => {
const response = await DayPilot.Http.get("/api/events");
setEvents(response.data);
};
loadEvents();
}, []);
Custom Event Color and “Edit” Icon
You can customize the appearance and behavior of individual events using the onBeforeEventRender
callback. This function is called just before each event is rendered, giving you full control over its style and interactive elements.
Example: Add a Custom Border and an Edit Icon
const onBeforeEventRender = (args) => {
// Set a dynamic border color (e.g., darker than bar color)
args.data.borderColor = "darker";
// Add a clickable edit icon to the right side of the event box
args.data.areas = [
{
right: 10,
top: "calc(50% - 10px)",
width: 20,
height: 20,
symbol: "/icons/daypilot.svg#edit", // Inline SVG symbol
onClick: async (args) => {
await editEvent(args.source); // Open custom modal
}
}
];
};
This areas
array defines active areas within the event box. In this case:
right: 10
places the icon 10px from the right edgetop: calc(50% - 10px)
centers it verticallyThe SVG symbol (edit icon) is clickable and triggers an
editEvent()
function when clicked
Attach the Callback to the Scheduler
Pass the function via the onBeforeEventRender
prop:
<DayPilotScheduler onBeforeEventRender={onBeforeEventRender} />
The editEvent()
Handler
You can define your own modal dialog or use the built-in DayPilot Modal:
const editEvent = async (e) => {
const form = [
{ name: "Text", id: "text" },
{ name: "Start", id: "start", type: "datetime", disabled: true },
{ name: "End", id: "end", type: "datetime", disabled: true },
{ name: "Resource", id: "resource", type: "select", options: resources },
{ name: "Color", id: "backColor", type: "select", options: colors }
];
const modal = await DayPilot.Modal.form(form, e.data);
if (!modal.canceled) {
scheduler.events.update(modal.result);
}
};
This lets users edit event details such as text, resource, or color directly from the timeline.
Additional Styling Options
Inside onBeforeEventRender
, you can also customize:
borderColor
,fontColor
cssClass
for advanced styling via CSShtml
for fully custom event contenttoolTip
to show more details on hover
Example with Full Styling
args.data.barColor = "#38761d";
args.data.fontColor = "#ffffff";
args.data.cssClass = "event-high-priority";
args.data.toolTip = "Click the pencil icon to edit this event.";
You can define the CSS class in your stylesheet:
.event-high-priority {
font-weight: bold;
background: linear-gradient(to right, #38761d, #6aa84f);
}
Full Source Code
Here is the full source code of the React Scheduler with a horizontal timeline:
import React, { useEffect, useState } from 'react';
import { DayPilot, DayPilotScheduler } from "@daypilot/daypilot-lite-react";
import "../assets/themes/dark.css";
import "../assets/themes/light.css";
import "../assets/toolbar.css";
const ReactScheduler = () => {
const [scheduler, setScheduler] = useState(null);
const [events, setEvents] = useState([]);
const [resources, setResources] = useState([]);
const [startDate, setStartDate] = useState(DayPilot.Date.today().firstDayOfYear());
const [days, setDays] = useState(DayPilot.Date.today().daysInYear());
const [theme, setTheme] = useState("light");
const themes = [
{ name: "light", text: "Light" },
{ name: "dark", text: "Dark" }
];
const colors = [
{ name: "(default)", id: null },
{ name: "Blue", id: "#2e78d6" },
{ name: "Green", id: "#6aa84f" },
{ name: "Yellow", id: "#f1c232" },
{ name: "Red", id: "#cc4125" }
];
const editEvent = async (e) => {
const form = [
{ name: "Text", id: "text" },
{ name: "Start", id: "start", type: "datetime", disabled: true },
{ name: "End", id: "end", type: "datetime", disabled: true },
{ name: "Resource", id: "resource", type: "select", options: resources },
{ name: "Color", id: "backColor", type: "select", options: colors }
];
const modal = await DayPilot.Modal.form(form, e.data);
if (modal.canceled) {
return;
}
scheduler.events.update(modal.result);
}
const onTimeRangeSelected = async (args) => {
const scheduler = args.control;
const modal = await DayPilot.Modal.prompt("Create a new event:", "Event 1");
scheduler.clearSelection();
if (modal.canceled) {
return;
}
scheduler.events.add({
start: args.start,
end: args.end,
id: DayPilot.guid(),
resource: args.resource,
text: modal.result
});
};
const onBeforeEventRender = (args) => {
args.data.borderColor = "darker";
// add edit icon to the event
args.data.areas = [
{
right: 10,
top: "calc(50% - 10px)",
width: 20,
height: 20,
symbol: "/icons/daypilot.svg#edit",
onClick: async (args) => {
await editEvent(args.source);
}
}
];
}
useEffect(() => {
const firstDayOfMonth = DayPilot.Date.today().firstDayOfMonth();
const events = [
{
id: 1,
text: "Event 1",
start: firstDayOfMonth.addDays(1),
end: firstDayOfMonth.addDays(6),
resource: "R2",
}
];
setEvents(events);
const resources = [
{ name: "Resource 1", id: "R1"},
{ name: "Resource 2", id: "R2"},
{ name: "Resource 3", id: "R3"},
{ name: "Resource 4", id: "R4"},
{ name: "Resource 5", id: "R5"},
{ name: "Resource 6", id: "R6"},
{ name: "Resource 7", id: "R7"},
{ name: "Resource 8", id: "R8"},
{ name: "Resource 9", id: "R9"},
];
setResources(resources);
scheduler?.scrollTo(DayPilot.Date.today().firstDayOfMonth());
}, [scheduler]);
return (
<div>
<div className="toolbar">
<label htmlFor="theme">Theme:</label>
<select
id="theme"
value={theme}
onChange={(e) => {
setTheme(e.target.value);
}}
>
{themes.map((t) => (
<option key={t.name} value={t.name}>
{t.text}
</option>
))}
</select>
</div>
<DayPilotScheduler
scale={"Day"}
timeHeaders={[
{groupBy: "Month"},
{groupBy: "Day", format: "d"}
]}
startDate={startDate}
days={days}
cellWidth={60}
events={events}
resources={resources}
onBeforeEventRender={onBeforeEventRender}
onTimeRangeSelected={onTimeRangeSelected}
controlRef={setScheduler}
theme={theme}
/>
</div>
);
}
export default ReactScheduler;