Scheduler
Resource timeline: people, rooms or machines down the side, time across the top, each booking a bar you can drag.
c2-scheduler lays bookings out per resource: a row for each person, room or machine, time across the top, each
booking a bar from its start to its end. Bookings that overlap on one resource stack in lanes and the row grows to
fit. Resources that share a group sit under a group row that collapses. The header pages through the range and
switches between the day, week and month views. With editable, users drag to create, move and resize
bookings; the scheduler reports each request and your app updates events. c2-week-planner stays the one-person
week, and c2-gantt the read-only project plan.
Import
import '@c2n/components/scheduler'Usage
Monday, 9 November 2026
9 – 15 November 2026
May 2026
Monday, 9 November 2026
Resources and events
resources is an array of { id, label, group?, description?, avatar?, color? }. A description adds a second line
under the label, avatar an image (the label’s initials otherwise), and color tints the avatar and the resource’s
bars. Ungrouped resources come first, then each group in the order it first appears.
events is an array of { id, resource, start, end, title, description?, color?, readonly?, pending? }. start and end are
wall-clock YYYY-MM-DDTHH:mm strings, the shape c2-date-time-picker uses, and every time the scheduler reports
comes back in that shape. A bar takes its color, else its resource’s, else --c2-scheduler__event--color. Both
properties accept JSON as an attribute.
Absences, holidays and a summary row
Three drawing options turn the scheduler into a team absence calendar:
- Pending. An event with
pending: trueis drawn hatched with a dashed edge (a request still to approve), and its name ends with “pending”.--c2-scheduler__event__pending--stripe-colorsets the hatching. - Bands.
bandsis an array of{ start, end, label?, color?, resource? }shaded under the bars: public holidays, a shutdown week, or withresourceone person’s non-working days. A band across every row also shades the header, where itslabelis the tooltip, and the slot cursor reads it. The weekend stays shaded as before. - Summary row.
renderSummarydraws a row under the grid with one cell per column. It is called with{ start, end, index, events, resources }: the column’s range and position, the events over it and every resource, and returns what to draw (“2 out”, a coverage warning).summary-headingnames the row. Give the content apartto style it.
Views and range
view:daydraws one column perslot-minutes(60 by default);weekandmonthdraw one column per day.date(YYYY-MM-DD) is a day of the range shown, today by default. The week starts on the locale’s first day.day-startandday-endbound the hours drawn each day;scroll-time(08:00) is where the day view opens.- The day view shades the hours outside
working-hours(09:00-18:00); every view shades the locale’s weekend. - A line marks the current time in
time-zone, drawn under the bars so it never crosses their text.
The header’s previous, Today and next buttons and its Day | Week | Month switch update date and view and fire
date-change and view-change with { date, view, start, end }, the moment to load that range’s bookings.
hide-header drops the header for an app that drives the range from its own toolbar.
Editing
editable lets users arrange bookings. The scheduler never changes events itself: it reports what the user asked
for, and you update events.
- Create. Dragging across empty time fires
range-selectwith{ resource, start, end }, snapped tosnap-minutes(15 by default). A click selects the column under the pointer. - Move and resize. Dragging a bar moves it in time and to another row; dragging either edge resizes it. Both fire
event-changewith{ event, start, end, resource }. A dashed preview follows the pointer, and Escape cancels. - Read-only. Bookings with
readonly: truestay in place.event-clickfires on any bar.
scheduler.addEventListener('event-change', ({ detail }) => {
const { event, start, end, resource } = detail
scheduler.events = scheduler.events.map((item) => (item === event ? { ...item, start, end, resource } : item))
})
scheduler.addEventListener('range-select', ({ detail }) => {
scheduler.events = [...scheduler.events, { id: crypto.randomUUID(), title: 'New booking', ...detail }]
})Keyboard and screen readers
The grid is one Tab stop holding a slot cursor. The arrow keys move it over every slot of a row, booked or free, Up
and Down move it to the same slot of the next row, and Home and End jump to the first and last slot. A booked slot is
read with its bookings. Enter on a booked slot focuses its first bar; on a free slot, with editable, Enter fires
range-select for it. On a bar, with editable, Left and Right move it by snap-minutes, Shift+Left and Shift+Right
change its end, and Alt+Up and Alt+Down move it to the previous or next resource; Up and Down go to the next row and
Escape goes back to the slot cursor. Each bar is a button named by its title, resource and time (“Kick-off, Ana Ruiz,
Monday 9 November, 09:00 to 10:30”), and moves and new bookings are announced.
Limits
Rows are not virtualised: the scheduler stays smooth up to about 200 resources and 2,000 bookings in view. Recurring
bookings are expanded by the app before they reach events.