Skip to content

Headless API

Everything here is imported from resource-scheduler/headless:

import { getColumns, getVisibleResources, layoutEvents, getRowHeights } from "resource-scheduler/headless";

The entry has no React, no CSS and no "use client", and its only dependency is date-fns. It runs in a browser, a server component, a route handler, a worker or any other framework. These are the functions the ResourceScheduler component is made of, so a UI built on them agrees with it. The guide Build your own UI puts them together.

Conventions: a Date is a local wall-clock time (for another zone, convert first with utcToZoned); an end is exclusive; a column is a slot of the day in the day view and a calendar day in the others.

Name Call What it does
ViewType ViewType.Day, Week, Month, Quarter, Year The views, as a const object ("day", "week", …). Also the type of one of them.
resolveSlotOptions resolveSlotOptions(options?) Returns { slotMinutes, dayStartHour, dayEndHour } with the defaults (60, 0, 24) filled in for anything missing or invalid.
getTimeSlots getTimeSlots(date, viewType, options?) The slots of the day view as Date[]; empty in the other views. options is SlotOptions.
getDatesInView getDatesInView(date, viewType, viewOptions?) The days of the period ([date] in the day view). viewOptions is ViewOptions: weekStartsOn, hideWeekends.
getColumns getColumns(date, viewType, options?) The columns of the grid: getTimeSlots in the day view, getDatesInView in the others. Takes SlotOptions and ViewOptions together.
getColumnRange getColumnRange(column, viewType, slotMinutes?) The { start, end } one column covers: a slot in the day view, the calendar day in the others.
getVisibleRange getVisibleRange(date, viewType, options?) The { start, end } the grid covers (end exclusive): the visible hours of the day, or the first to the last day shown.
navigateDate navigateDate(date, viewType, "prev" | "next") A day, week, month, quarter or year earlier or later, by view.
isToday isToday(date, now?) Whether date is on the same calendar day as now (default: the current time).
Name Call What it does
withEvents withEvents(resources, events?) Adds a flat event list to the resources named by resourceId / resourceIds. Returns Resource[]; a resource without events gets an empty array.
expandRecurrence expandRecurrence(event, from, to) The occurrences of one event that touch from to to (end exclusive). Ids are <id>::<yyyy-MM-dd> (hourly: <id>::<yyyy-MM-dd>T<HH:mm>) with seriesId.
expandEvents expandEvents(events, from, to) expandRecurrence over a list; events that do not recur pass through.
splitRecurrence splitRecurrence(event, occurrence, edits?) “Edit this and following”: { before, after }, the series ended the day before (null for the first occurrence) and a new series from the occurrence with the edits applied. Throws a RangeError if occurrence is not one.
editOccurrence editOccurrence(event, occurrence, edits?) “This occurrence only”: { series, changed }, the series with an exception and a one-off event for that day.
getVisibleEvents getVisibleEvents(events, date, viewType, slotOptions?, viewOptions?) The events that touch what the view shows: the visible hours in the day view, the shown days in the others.
getVisibleResources getVisibleResources(resources, date, viewType, options?) The resources as the grid draws them: recurring events expanded for the visible range and the events outside the view left out. New objects; the input is not changed.
withGroups withGroups(resources, collapsed?) Lays resources out under group header rows (Resource.group, as a name or a path). Members of a collapsed group (by key) are left out. Returns resources itself when none has a group.
isGroupRow isGroupRow(row) Whether a row from withGroups is a group header (a GroupRow, with groupHeader: { name, key, depth, count, collapsed }) and not a resource.
GROUP_ROW_HEIGHT 36 The height of a group header row, in px.
Name Call What it does
layoutEvents layoutEvents(events, columns, viewType) Places events on the columns: returns EventPosition[] of { event, startPosition, span, lane }, sorted by start. Overlapping events go to the first lane that is free, so a row needs one lane per level of overlap.
getEventSpan getEventSpan(event, columns, viewType) How many columns one event covers, clamped to the columns shown (at least 1).
getEventStartPosition getEventStartPosition(event, columns, viewType) The index of the first column of one event, clamped to the columns shown.
getRowHeight getRowHeight(row, columns, viewType, options?) The height of a row in px: a lane for each level of overlap plus ROW_PADDING, never below minRowHeight; a group header gets GROUP_ROW_HEIGHT. options is RowHeightOptions: lane (default 52) and minRowHeight (default 0).
getRowHeightFromLayout getRowHeightFromLayout(row, positions, options?) getRowHeight for a row whose events you have already laid out with layoutEvents, so they are not laid out twice.
getRowHeights getRowHeights(rows, columns, viewType, options?) getRowHeight for every row, as number[].
getGridTemplateRows getGridTemplateRows(heights) The CSS grid-template-rows for a header (auto) above rows of those heights: "auto 60px 112px".
getTimePosition getTimePosition(date, range) How far date is into range, as a percentage; null outside (the end is exclusive). For a “now” line.
LANES LANES.comfortable, LANES.compact { lane, card } in px: the height of a lane of events (52 or 38) and of the card inside it (48 or 34). The Density type names the keys.
ROW_PADDING 8 The space below the lanes of a row, in px.
UTILIZATION_ROW_HEIGHT 60 The least a row with a utilization bar may be, in px.
COLUMN_UTILIZATION_HEIGHT 44 The height of the row of column totals, in px.
getRowWindow getRowWindow(heights, top, height, overscan?, offset?) The rows worth drawing for a scroll position, as { start, end } (end exclusive). offset is what sits above the first row.
getColumnWindow getColumnWindow(count, columnWidth, left, width, overscan) The columns in a viewport that starts left px into a grid of equal columns, as { start, end } (end exclusive). Returns every column when it cannot tell.
parsePx parsePx(width) "90px" as 90; undefined for any other unit.

The pointer handling is yours. These turn “the slot under the pointer” into a range.

Name Call What it does
getMovePlacement getMovePlacement(event, target, viewType) Where event lands when dropped on target (DragTarget: { resourceId, slot }): that resource, the same length. The day view puts the start on the slot; the others keep the time of day. Returns a Placement.
getResizePlacement getResizePlacement(event, resourceId, edge, target, viewType, slotMinutes?) The placement after dragging "start" or "end" to target, the slot under the pointer included. null on another resource’s row, or when the edge would cross the other one.
isSamePlacement isSamePlacement(a, b) Whether two placements (or two nulls) are the same resource and times.
getDropRange getDropRange(event, slot, viewType) The { start, end } of a move without the resource: what getMovePlacement uses.
getResizeRange getResizeRange(event, edge, slot, viewType, slotMinutes?) The { start, end } of a resize, or null when the edge would cross. The day view snaps to slots; the others move whole days.
getSelectionBounds getSelectionBounds(a, b, viewType, slotMinutes?) The range covered by dragging from slot a to slot b, last slot included, either direction.
isSlotInRange isSlotInRange(slot, range, viewType, slotMinutes?) Whether the slot or day starting at slot is touched by range. For drawing the footprint of a drag.
getSelectedColumns getSelectedColumns(columns, from, to, viewType) The first and last column index between two slots, as { start, end }; null when either is not a column.
stepPlacement stepPlacement(placement, step, { viewType, slotMinutes, resourceIds, range }) The placement after a keyboard step (Step: cols, rows, resizeEnd), or null when it would leave the range or the rows, or end with no length.
moveCursor moveCursor(cursor, key, rows, cols) The new Cursor ({ row, col }) for an arrow, Home or End key, clamped to the grid; null for other keys.
flipHorizontal flipHorizontal(key, rtl) Swaps ArrowLeft and ArrowRight in a right-to-left layout.
Name Call What it does
isPlacementAllowed isPlacementAllowed(event, placement, resources, rules) The check the component runs before a move, resize or create. rules is PlacementRules: eventOverlap, isValidDrop. Use it on a server too.
rangesOverlap rangesOverlap(aStart, aEnd, bStart, bEnd) Whether two ranges overlap; ends are exclusive and a zero-length range is one instant.
isCellUnavailable isCellUnavailable(resource, cell, { businessHours, viewType, slotMinutes? }) Whether the slot or day starting at cell is shaded for that resource.
touchesUnavailable touchesUnavailable(resource, { start, end }, context) Whether a placement touches a shaded cell. What blockUnavailable runs.
resolveBusinessHours resolveBusinessHours(hours?) The BusinessHours with the defaults filled in (Monday to Friday, 9 to 17).
findAvailableSlots findAvailableSlots(resource | resources, { from, to, duration, step?, businessHours?, limit? }) The free { start, end } slots, earliest first; with several resources, the times when all are free. FindSlotsOptions is the options type.
getUtilization getUtilization(resource, range, { businessHours? }) { bookedMinutes, availableMinutes, ratio } over a range (end exclusive); ratio is null when nothing is available. Returns a Utilization.
columnUtilization columnUtilization(resources, columns, { businessHours? }) One Utilization per column range, over all the resources together.
groupUtilization groupUtilization(resources, range, { businessHours? }) A Map from a group header’s id to the Utilization of everything below it.

The four steps of Build your own UI, kept up to date as a store that no framework is part of. In React, useSchedulerState is this store read through useSyncExternalStore.

Name Call What it does
createSchedulerStore createSchedulerStore({ resources, events?, initialDate?, initialView?, ...options }) Returns a SchedulerStore. The input is SchedulerStoreInput: the data (resources, flat events, collapsedGroups), the day and week options (slotDuration, dayStartHour, dayEndHour, weekStartsOn, hideWeekends), businessHours, density and showUtilization. Starts on initialDate (default now) in initialView (default the day view).

A SchedulerStore has:

  • getState(): the current SchedulerState, the same object until something changes. It holds date, view, range (end exclusive), columns, slotMinutes, rows (group headers and resources, recurring events expanded, only the events in view), layout (the EventPosition[] of each row, same index as rows), rowHeights (px) and utilization (a Map by row id, with showUtilization).
  • subscribe(listener): calls listener after each change and returns the function that stops it. With getState this is all an adapter needs: React’s useSyncExternalStore, a Vue shallowRef, a Svelte readable.
  • setDate(date), setView(view), navigate("prev" | "next") (returns the new date) and goToToday(now?).
  • setInput(input, { silent? }): takes new data or options, only the ones you pass. Nothing happens when they are what they were; silent skips the notification for a host that is already rendering.

Every part of the state is kept until the parts it depends on change: a new date keeps the resources, new resources keep the columns.

Name Call What it does
utcToZoned utcToZoned(date, timeZone) A Date whose local fields are the wall-clock time in the IANA zone at that instant. Not an instant: convert back before storing.
zonedToUtc zonedToUtc(date, timeZone) The instant at which a clock in the zone shows the local fields of date.
isValidTimeZone isValidTimeZone(name) Whether the runtime knows the IANA name.
Name Call What it does
buildI18n buildI18n({ locale?, hour12?, labels?, dir? }?) The language and clock, resolved: { locale, hour12, dir, labels, fmt }. fmt is a Formatter with time, hourHeader, weekday, dayOfMonth, monthShort, monthYear, quarterYear, titleDay and more. The result type is ResolvedI18n; the input is I18nInput.
defaultLabels defaultLabels.today, .views.week, … Every word and screen reader message, in English (SchedulerLabels). Override any subset through buildI18n({ labels }) (PartialLabels).
formatPeriodTitle formatPeriodTitle(date, viewType, viewOptions?, i18n?) The title of the period: "October 7, 2026", "Oct 5 – Oct 9, 2026", "October 2026", "Q4 2026", "2026".
formatSlotLabel formatSlotLabel(slot, viewType, i18n?) The screen reader label of a slot.
formatRangeLabel formatRangeLabel(start, end, viewType, i18n?) The screen reader label of a range (end exclusive).
formatEventTime formatEventTime(event, i18n?) "9:30 AM – 11:00 AM", or the all-day label.
Name What it is
SchedulerEvent An event: id, title, startDate, endDate, and optionally color, description, resourceId, resourceIds, recurrence, seriesId.
Resource A row: id, name, events, and optionally role, businessHours, unavailable, capacity, group.
ResourceInput A Resource whose events may be left out (for withEvents).
Recurrence How an event repeats: freq (hourly to yearly), interval, byWeekday, byMonthDay, bySetPos, until, count, exceptions.
BusinessHours Working time: daysOfWeek, startHour, endHour.
UnavailableRange { start, end } a resource cannot be booked.
SlotSelection { resourceId, start, end } of selected empty slots.
VisibleRange { start, end } the grid covers; end is exclusive.
SlotOptions slotDuration, dayStartHour, dayEndHour.
ViewOptions weekStartsOn, hideWeekends.
Placement { resourceId, start, end }: where an event would end up.
PlacementRules eventOverlap and isValidDrop.
DragTarget { resourceId, slot }: the slot under the pointer.
Utilization { bookedMinutes, availableMinutes, ratio }.
SchedulerStoreInput What createSchedulerStore takes: a SchedulerInput plus initialDate and initialView.
SchedulerInput The data and options of a view: resources, events, collapsedGroups, slotDuration, dayStartHour, dayEndHour, weekStartsOn, hideWeekends, businessHours, density, showUtilization. What setInput takes (any of them).
SchedulerState A snapshot of the view: date, view, range, columns, slotMinutes, rows, layout, rowHeights, utilization.
SchedulerStore The store: getState, subscribe, setInput, setDate, setView, navigate, goToToday.
GroupRow A group header row from withGroups.
EventPosition { event, startPosition, span, lane } from layoutEvents.
RowHeightOptions lane and minRowHeight for getRowHeight.
FindSlotsOptions The options of findAvailableSlots.
Step A keyboard step: cols, rows, resizeEnd.
StepContext What stepPlacement needs: viewType, slotMinutes, resourceIds, range.
Cursor { row, col } of the keyboard cursor.
Density "comfortable" or "compact", the keys of LANES.
SchedulerLabels Every label the scheduler shows or reads out.
PartialLabels Any subset of SchedulerLabels.
I18nInput What buildI18n takes.
Formatter The date and time formats of buildI18n(...).fmt.
ResolvedI18n What buildI18n returns.