Building a resource scheduling interface from scratch is harder than it looks. The time axis, the row layout, overlapping events, drag precision, resizing each piece is a rabbit hole. Most developers underestimate it until they're three weeks in.
In this tutorial, you'll build a fully working drag-and-drop resource scheduler in React using Bryntum Scheduler in under 30 minutes, with no backend required. You'll have a polished team scheduler where events can be dragged across resources and time slots — and you'll understand exactly how it works.
What we're building
A team scheduling view with:
- A resource grid on the left listing team members
- A timeline on the right showing tasks as draggable event bars
- Drag to move events to a new time or a different team member
- Click an empty area to create new events, double-click to edit them
Prerequisites
- Node.js 20+
- Basic React knowledge (hooks, components)
- No Bryntum experience needed
Create a React App
We'll use Vite for its fast dev server and minimal config:
npm create vite@latest bryntum-scheduler-demo -- --template react
cd bryntum-scheduler-demo
npm install
Install Bryntum Scheduler
Bryntum Scheduler comes as two packages: the core library and a React wrapper. You can get started immediately using the free trial packages available on the public npm registry — no account or license key needed:
npm install @bryntum/scheduler-trial @bryntum/scheduler-react
When you're ready for production, you swap these for the licensed packages (@bryntum/scheduler). The API is identical, so there's nothing to change in your code.
Import a CSS Theme
Bryntum Scheduler comes with several built-in themes. Import one in your src/index.css by replacing it with the following lines:
@import "@bryntum/scheduler/fontawesome/css/fontawesome.css";
@import "@bryntum/scheduler/fontawesome/css/solid.css";
@import "@bryntum/scheduler/scheduler.css";
@import "@bryntum/scheduler/svalbard-light.css";
The svalbard theme is Bryntum's modern default. Other options include visby, stockholm, material, and high-contrast — switching is as simple as changing the import.
Build a Bryntum Scheduler
Create src/schedulerConfig.js with the following:
export const schedulerConfig = {
// The visible time range
startDate : new Date(2026, 5, 16, 7, 0),
endDate : new Date(2026, 5, 16, 19, 0),
// How the time axis is divided — 'hourAndDay' shows hours on top
viewPreset : 'hourAndDay',
// Left-side grid columns
columns : [
{
type : 'resourceInfo', // shows avatar + name
text : 'Team Member',
field : 'name',
width : 180,
},
{
text : 'Role',
field : 'role',
width : 120,
}
],
// Row and bar sizing
rowHeight : 50,
barMargin : 4,
// Images path
resourceImagePath: 'https://bryntum.com/products/scheduler/examples/_shared/images/transparent-users',
};
The viewPreset controls how the time axis is divided. 'hourAndDay' shows individual hours, which works well for daily scheduling. For longer time ranges, 'dayAndWeek' or 'weekAndMonth' are more appropriate.
Create src/TeamScheduler.jsx. This is the core of the app:
import { useRef, useCallback } from 'react';
import { BryntumScheduler } from '@bryntum/scheduler-react';
import { schedulerConfig } from './schedulerConfig';
export default function TeamScheduler() {
const schedulerRef = useRef(null);
// Fires after a successful drag-and-drop
const onEventDrop = useCallback(({ eventRecords, resourceRecord }) => {
const event = eventRecords[0];
console.log(
`"${event.name}" moved to ${resourceRecord.name}`,
`— ${event.startDate.toLocaleTimeString()} to ${event.endDate.toLocaleTimeString()}`
);
}, []);
return (
<BryntumScheduler
ref = {schedulerRef}
{...schedulerConfig}
// React to drop events
onEventDrop = {onEventDrop}
/>
);
}
A few things worth calling out here. The BryntumScheduler component is a React wrapper around the underlying high-performance JS engine, you configure it entirely through props, and it handles all rendering and interaction internally.
Drag-and-drop is enabled by default. You don't configure a feature flag or install a plugin - events can be dragged to a new time slot or a different resource row the moment they appear on screen. The onEventDrop callback is where you react to the change, typically by sending an update to your backend.
Write it into your app
Replace src/App.jsx with this:
import TeamScheduler from './TeamScheduler';
import './App.css';
function App() {
return (
<div className="app">
<TeamScheduler />
</div>
);
}
export default App;
Then replace src/App.css with the styles for the layout and header:
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
margin: 0;
font-family: system-ui, sans-serif;
}
.app {
display: flex;
flex-direction: column;
height: 100vh;
}
You'll see an empty Scheduler.
Understanding the Data Model
Before adding the data, it helps to understand how Bryntum Scheduler thinks about data. There are two core concepts.
Resources are the rows — each person, room, or machine that gets scheduled:
{ id: 'r1', name: 'Alice Johnson', role: 'Designer' }
Events are the bars on the timeline. Each event references a resource via resourceId, and defines its position in time with startDate and endDate:
{
id: 'e1',
resourceId: 'r1',
name: 'Design Review',
startDate: new Date(2026, 5, 16, 9, 0), // June 16, 9:00 AM
endDate: new Date(2026, 5, 16, 11, 0), // June 16, 11:00 AM
}
Bryntum Scheduler has several built-in stores but to keep it simple, let's stick to these two, they're enough to build us a simple scheduler.
Define your Data
Create a public/data.json file with the following data:
{
"resources": {
"rows": [
{ "id": "r1", "name": "Alice Johnson", "role": "Designer", "image": "lee.png" },
{ "id": "r2", "name": "Bob Smith", "role": "Developer", "image" : "celia.png"},
{ "id": "r3", "name": "Carol White", "role": "Developer", "image" : "dan.png"},
{ "id": "r4", "name": "David Lee", "role": "QA Engineer", "image" : "gloria.png"},
{ "id": "r5", "name": "Eva Martinez", "role": "Designer", "image" : "kate.png" }
]
},
"events": {
"rows": [
{ "id": "e1", "resourceId": "r1", "name": "Design Review", "startDate": "2026-06-16T09:00:00", "endDate": "2026-06-16T11:00:00", "eventColor": "blue" },
{ "id": "e2", "resourceId": "r1", "name": "Client Call", "startDate": "2026-06-16T13:00:00", "endDate": "2026-06-16T14:00:00", "eventColor": "orange" },
{ "id": "e3", "resourceId": "r2", "name": "Sprint Planning", "startDate": "2026-06-16T09:00:00", "endDate": "2026-06-16T10:30:00", "eventColor": "green" },
{ "id": "e4", "resourceId": "r2", "name": "Code Review", "startDate": "2026-06-16T14:00:00", "endDate": "2026-06-16T16:00:00", "eventColor": "green" },
{ "id": "e5", "resourceId": "r3", "name": "API Integration", "startDate": "2026-06-16T10:00:00", "endDate": "2026-06-16T13:00:00", "eventColor": "indigo" },
{ "id": "e6", "resourceId": "r4", "name": "Regression Testing", "startDate": "2026-06-16T09:00:00", "endDate": "2026-06-16T12:00:00", "eventColor": "red" },
{ "id": "e7", "resourceId": "r4", "name": "Bug Verification", "startDate": "2026-06-16T13:30:00", "endDate": "2026-06-16T15:30:00", "eventColor": "red" },
{ "id": "e8", "resourceId": "r5", "name": "UI Prototyping", "startDate": "2026-06-16T11:00:00", "endDate": "2026-06-16T15:00:00", "eventColor": "purple" }
]
}
}
The eventColor field controls the bar color. Bryntum ships with a full palette — blue, green, red, orange, indigo, purple, cyan, teal, and more.
Import it by adding the following to the end of schedulerConfig in src/schedulerConfig.js:
// Load the data.json
crudManager : {
loadUrl : 'data.json',
autoLoad : true
},
Run it
npm run dev
Open http://localhost:5173. You'll see your team and their tasks laid out on the timeline. Drag any event bar - it snaps to the time axis and can be dropped onto any resource row.
What you get without writing another line
This is where Bryntum Scheduler earns its keep. Without any additional configuration, you already have:
- Drag to move: drag an event to any row or time position
- Drag to resize: pull the right edge of a bar to extend or shrink its duration
- Click to create: click an empty area on the timeline to create a new event
- Double-click to edit: opens a polished event editor popup with name, time, and color fields
- Automatic layout: events in the same row stack cleanly without overlap
- Event tooltips: hover to see event details without opening the editor
Your Scheduler is Ready
That's it - a fully functional drag-and-drop resource scheduler running entirely in the browser, no backend required. All changes live in-memory, which is perfect for prototyping or client-side-only tools.
When you're ready to persist data, Bryntum makes that straightforward too. Connecting the scheduler to a real backend, including the built-in CrudManager for batched syncing, is something really simple which we will cover in some other blog post.
Where to Go Next
Now that you have a working scheduler, here are natural next steps to explore:
Connect a real backend — Bryntum's built-in
CrudManagerhandles syncing creates, updates, and deletes in a single batched request, making backend integration straightforward.Custom event rendering — the
eventRendererprop accepts a function that returns custom JSX, giving you full control over how event bars look.Resource grouping — group rows by team or role with grouping option.
Time ranges — highlight standups, lunch breaks, or blocked hours using the timeRanges feature without touching your event data.



Top comments (0)