Replace parts of the scheduler
classNames restyles the parts. When styling is not enough, components replaces a part with a component of yours.
import type { SchedulerComponents } from "resource-scheduler";
const ResourceHeader: SchedulerComponents["ResourceHeader"] = ({ resource }) => ( <div className="flex items-center gap-2"> <Avatar name={resource.name} /> <span>{resource.name}</span> </div>);
// Outside your component, or memoized: see "Keep in mind" below.const components: SchedulerComponents = { ResourceHeader, Event, EventPopover, Toolbar };
<ResourceScheduler resources={resources} components={components} />;Give only the parts you want to change. Everything else stays as it is, and without components nothing changes.
The parts
Section titled “The parts”| Part | You get | Replaces |
|---|---|---|
Toolbar |
ToolbarProps: title, viewTitle, views, viewType, currentDate, onNavigate, onViewChange, onGoToToday, labels, dir, allowViewChange |
The bar with the date, arrows, Today and the view select |
Event |
{ event, resource } |
The content of an event card |
EventPopover |
{ event, resource, close } |
The details that open from an event |
ResourceHeader |
{ resource } |
The content of a resource’s cell in the left column |
DateHeader |
{ date, view } |
The content of a column header |
EmptyCell |
{ date, resource } |
What is drawn inside every empty slot |
Event replaces the content, not the card. The card around it keeps its size, its drag and resize handles, its keyboard use (Enter opens the details, Space picks it up), its focus ring and its screen reader name, so your event looks the way you want and still behaves. Size your content to fill the card (height: 100%).
Toolbar replaces the whole bar, so you build the controls and the scheduler gives you what they need. title is the period as the locale writes it (“Oct 5 – Oct 9, 2026”), viewTitle the view in words, views the views to offer (availableViews, or all five). Navigation goes through the callbacks: onNavigate("prev" | "next"), onViewChange(view) and onGoToToday(). labels has every word in the language the scheduler was given, and dir is "rtl" when the layout is mirrored, so turn your arrows the other way. In a custom toolbar classNames.toolbar has nothing to style.
const Toolbar: SchedulerComponents["Toolbar"] = ({ title, views, viewType, onNavigate, onViewChange, labels }) => ( <header> <button onClick={() => onNavigate("prev")}>{labels.previousPeriod}</button> <h2>{title}</h2> <button onClick={() => onNavigate("next")}>{labels.nextPeriod}</button> {views.map((view) => ( <button key={view} aria-pressed={view === viewType} onClick={() => onViewChange(view)}> {labels.views[view]} </button> ))} </header>);Why components and not render props
Section titled “Why components and not render props”The render* props (renderTimeSlot, renderEventPopover, renderResourceHeader, renderDateHeader, renderEmptyCell) still work. A part in components wins over the matching render prop when both are given. The difference is that a component is a component:
- It can use hooks and keep state. A render prop is called as a plain function inside the scheduler’s own render, so a hook in it belongs to the scheduler’s component: lint rules flag it, and it breaks as soon as the prop is added or removed later. The popover in the demo above keeps its “Done” state in
useState. - It shows up in React DevTools with a name, can be memoized, and can be shared with the rest of your app.
- There is one prop to learn instead of five.
Keep in mind
Section titled “Keep in mind”- Define components outside your render, or memoize them. A component created inside your component is a new type on every render, and React throws away and recreates it (and its state) each time. Passing a fresh
components={{ Event }}object each render is fine as long asEventitself is the same function. - Dates are wall-clock Dates with
timeZone, like the render props:format(event.startDate, "p")shows the zone’s time. See Time zones. - Clicks inside an
Eventalso reach the card: a click opens the popover and a press starts a drag. For a button inside the card, callevent.stopPropagation()on itspointerdownandclick, or keep the card for interaction and use the popover for controls. - A custom
Toolbarprints as you wrote it. Put the classrs-no-printon its controls to leave them off the page; see Printing. - Group headers are not
ResourceHeader: they are the scheduler’s own bands, styled throughclassNames.groupHeader. ResourceHeadergets the resource only. WithshowUtilizationthe bar under the name stays.
Not included
Section titled “Not included”- Replacing the grid, a slot, the resource column or the group headers as a whole. Those carry the layout, the keyboard grid and the drag logic; use
classNamesand the tokens for them. - Replacing the markup of the grid altogether. For that, build on the logic without the component: Build your own UI draws a timeline from the functions of
resource-scheduler/headless(layout, lanes, row heights, recurrence, rules).