v1.6.0

<bmx-scheduler>

A resource scheduler: rooms, people and equipment down the side, time across, and bookings on them - made by dragging over free time, moved and resized by dragging or from the keyboard, and checked as they are made. Day, week and month timelines, and an agenda list (which a narrow screen gets on its own).

31 properties · 6 events · 21 methods · 21 parts

Example

Drag over free time to book it, drag a booking to move it (onto another row to change who or what it books), drag its ends to resize it, and double-click to edit. Overlaps beyond what a resource can hold are outlined as clashes; shaded time is outside working hours. Try Find a time for the first hour the board room, Asha and Ben are all free.
Show markup
<bmx-scheduler
  id="ex-sch"
  label="Office bookings"
  view="week"
  day-start="07:00"
  day-end="20:00"
  holidays="2026-12-25,2026-12-28"
  style="--bmx-scheduler-height: 30rem"
></bmx-scheduler>

<div class="row" style="margin-block-start: 1rem">
  <span class="note" id="ex-sch-out">
    <strong>Drag over free time to book it</strong>, drag a booking to move it (onto another row to change who or
    what it books), drag its ends to resize it, and double-click to edit. Overlaps beyond what a resource can hold are
    outlined as clashes; shaded time is outside working hours. Try <em>Find a time</em> for the first hour the board
    room, Asha and Ben are all free.
  </span>
</div>

<script type="module">
  await customElements.whenDefined('bmx-scheduler');
  const scheduler = document.getElementById('ex-sch');
  const out = document.getElementById('ex-sch-out');

  // This week's Monday, so the example always shows bookings.
  const today = new Date();
  const monday = new Date(today.getFullYear(), today.getMonth(), today.getDate() - ((today.getDay() + 6) % 7));
  const day = n => {
    const d = new Date(monday.getFullYear(), monday.getMonth(), monday.getDate() + n);
    return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
  };

  scheduler.resources = [
    { id: 'board', name: 'Board room', group: 'Rooms', details: '12 seats', color: '#1971c2' },
    { id: 'lab', name: 'Training lab', group: 'Rooms', details: '3 bays', maxConcurrent: 3, color: '#2f9e44',
      unavailable: [{ start: `${day(2)}T12:00`, end: `${day(2)}T14:00`, title: 'Deep clean' }] },
    { id: 'asha', name: 'Asha Rao', group: 'People', details: 'Bengaluru', timeZone: 'Asia/Kolkata', workingHours: 'Mon-Fri 09:30-18:00', color: '#9c36b5' },
    { id: 'ben', name: 'Ben Hughes', group: 'People', details: 'Engineer', workingHours: 'Mon-Thu 08:00-16:00', color: '#e8590c' },
    { id: 'van', name: 'Delivery van', group: 'Equipment', color: '#0c8599', workingHours: 'Mon-Sat 07:00-19:00' },
  ];

  scheduler.events = [
    { id: 'standup', title: 'Stand-up', start: `${day(0)}T09:30`, end: `${day(0)}T09:45`, resourceIds: ['board', 'ben'],
      recurrence: 'FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR' },
    { id: 'review', title: 'Quarterly review', start: `${day(1)}T10:00`, end: `${day(1)}T12:00`, resourceIds: ['board', 'asha', 'ben'] },
    { id: 'clash', title: 'Supplier call', start: `${day(1)}T11:00`, end: `${day(1)}T11:45`, resourceId: 'board' },
    { id: 'course', title: 'Safety course', start: `${day(2)}T09:00`, end: `${day(2)}T16:00`, resourceId: 'lab' },
    { id: 'mentor', title: 'Mentoring', start: `${day(3)}T14:00`, end: `${day(3)}T15:00`, resourceIds: ['asha', 'ben'], status: 'tentative' },
    { id: 'round', title: 'Delivery round', start: `${day(0)}T08:00`, end: `${day(0)}T12:30`, resourceId: 'van',
      recurrence: 'FREQ=WEEKLY;BYDAY=MO,WE,FR;COUNT=12' },
    { id: 'leave', title: 'Annual leave', start: day(4), end: day(5), allDay: true, resourceId: 'asha' },
  ];

  scheduler.addEventListener('bmxSchedulerChange', event => {
    out.textContent = `bmxSchedulerChange - ${event.detail.action}; ${event.detail.events.length} bookings to save.`;
  });
  scheduler.addEventListener('bmxSchedulerEventChange', event => {
    if (event.detail.conflicts.length) out.textContent = `bmxSchedulerEventChange - this would clash (${event.detail.conflicts.length}).`;
  });
</script>

DATA

resources are things that can be booked, each with an id, a name, an optional group (Rooms, Engineers), maxConcurrent (how many bookings at once - more is a clash), workingHours (Mon-Fri 09:00-17:00), timeZone and unavailable times. events are bookings, each with an id, a title, start and end (2026-10-05T09:00 is that wall time, ...Z an instant, 2026-10-05 a whole day), and resourceId - or resourceIds for a booking that takes a room and two people at once. Changes come back in bmxSchedulerChange as the whole list, written in the forms they came in.

REPEATING

recurrence is an iCalendar rule - FREQ=WEEKLY;BYDAY=MO,WE - so series go to and from any calendar program. Moving or editing one occurrence asks whether to change it alone, it and the following ones, or the whole series (or follows recurrence-edits).

CHECKS

Bookings over a resource's limit, or in its unavailable time, are clashes: outlined, counted in the status bar, and - with conflicts="prevent" - refused. outside-hours does the same for bookings outside working hours, which are shaded. Each resource shows its utilisation: booked working time over working time in the range.

TIME ZONES

Everything is drawn in time-zone (the reader's by default). A resource with its own zone has its working hours read in it, and shows its local time. Clock changes are handled: a weekly 09:00 meeting stays at 09:00.

MORE

"Find a time" finds the earliest time a set of resources are free together (or any one of them); findSlot() does it from script. toICS() and the toolbar's export write an .ics file; loadICS() reads one. Undo and redo cover every change. The built-in editor can be replaced by cancelling bmxSchedulerEventActivate.

KEYBOARD

Tab reaches the bookings; arrow keys move between them (left and right in a row, up and down between rows); Enter opens one; Delete removes it. Alt+arrows move a booking by a slot or to the next resource, and Alt+Shift+Left/Right make it shorter or longer. Page Up and Page Down go to the previous or next range; Ctrl+Z and Ctrl+Y undo and redo.

Properties

PropertyAttributeTypeDefaultDescription
conflicts conflicts 'allow' | 'warn' | 'prevent' 'warn' Clashes: allow (not shown), warn (shown and announced) or prevent (refused).
currentTime current-time string — The moment taken as now (ISO), for repeatable pages and tests. Default: the clock.
date date string — A date in the range on show (2026-10-05). Default: today.
dayEnd day-end string '24:00' See day-start.
dayStart day-start string '00:00' The part of each day drawn in the day and week views: 07:00 to 20:00, say.
dayWidth day-width number 56 Pixels per day in the month view.
defaultDuration default-duration number 60 Minutes a booking lasts when made by a double-click, or given without an end.
editor editor boolean true Whether double-click and Enter open the built-in editor.
events events BmxSchedulerEvent[] | string [] Bookings: [{ id, title, start, end?, resourceId | resourceIds, recurrence?, ... }], as a property or JSON. Changes write back here.
firstDayOfWeek first-day-of-week number — 0 for Sunday to 6 for Saturday. Default: the locale's.
groupBy group-by string 'group' The resource field resources are grouped by; empty for no groups.
holidays holidays string[] | string [] Dates no one works (2026-12-25): a list, or text separated by commas.
hourWidth hour-width number — Pixels per hour in the day and week views. Default: 72 for a day, 16 for a week.
label label string — An accessible name for the scheduler.
locale locale string — A BCP 47 locale for dates and times. Default: the page's.
outsideHours outside-hours 'allow' | 'warn' | 'prevent' 'allow' Bookings outside working hours: allow, warn or prevent.
readonly readonly boolean false No making, moving, resizing or deleting.
recurrenceEdits recurrence-edits 'ask' | 'occurrence' | 'series' 'ask' Changing one occurrence of a series: ask, or always the occurrence or the series.
requestInit request-init RequestInit | string — Options for the src request (headers, credentials), as a property or JSON.
resourceWidth resource-width number 220 The width of the resource column, in pixels.
resources resources BmxSchedulerResource[] | string [] Things that can be booked: [{ id, name, group?, maxConcurrent?, workingHours?, timeZone?, unavailable? }], as a property or JSON.
responsive responsive boolean true A narrow scheduler (under 640 pixels) shows the agenda.
rowHeight row-height number 44 The least a row is high, in pixels. Rows grow to hold overlapping bookings.
showUtilisation show-utilisation boolean true Whether each resource shows its utilisation.
slotMinutes slot-minutes number 15 The step bookings snap to, in minutes.
src src string — A URL to load { resources, events } from as JSON, or bookings from an .ics file.
strings strings Partial<BmxSchedulerStrings> | string — Words to show instead of the English ones, as a property or JSON.
timeZone time-zone string — The zone everything is drawn in (IANA name: Europe/London). Default: the reader's.
view view BmxSchedulerView 'week' day, week, month or agenda.
workWeek work-week boolean false The week view shows only working days (those in working-hours).
workingHours working-hours string | BmxSchedulerWorkingSpan[] 'Mon-Fri 09:00-17:00' When resources work, unless they say otherwise: text like Mon-Fri 09:00-17:00, or spans.

Events

EventDetailDescription
bmxSchedulerChange BmxSchedulerChangeDetail Bookings changed: the whole new list.
bmxSchedulerEventActivate BmxSchedulerActivateDetail A booking was opened. Cancel it to show your own editor.
bmxSchedulerEventChange BmxSchedulerEventChangeDetail A booking is about to change. Cancel it to refuse the change.
bmxSchedulerEventCreate BmxSchedulerCreateDetail A reader asked for a new booking. Cancel it to make your own.
bmxSchedulerRangeChange BmxSchedulerRangeChangeDetail The range on show changed.
bmxSchedulerSelect BmxSchedulerSelectDetail The chosen booking changed.

Methods

MethodSignatureDescription
addEvent addEvent(event: BmxSchedulerEvent) => Promise<boolean> Adds a booking. False when it is refused (by a cancelled bmxSchedulerEventChange or a clash under conflicts="prevent").
downloadICS downloadICS(fileName?: string) => Promise<void> Saves every booking as an .ics file through the browser's download.
findSlot findSlot(request: Omit<BmxSchedulerSlotRequest, "from" | "to"> & { from?: string | number | Date; to?: string | number | Date; }) => Promise<{ start: string; end: string; resourceIds: string[]; } | null> The earliest time resources are free for a booking: all of them, or any one with match: 'any', in working hours unless workingHoursOnly is false. Times come back as ISO instants. Null when there is none in the next 90 days (or before to).
getConflicts getConflicts(from?: string | number | Date, to?: string | number | Date) => Promise<BmxSchedulerConflict[]> The clashes between two times (default: the range on show).
getEvents getEvents() => Promise<BmxSchedulerEvent[]> Every booking, as the page would save it.
getOccurrences getOccurrences(from?: string | number | Date, to?: string | number | Date) => Promise<BmxSchedulerOccurrence[]> The bookings between two times (default: the range on show), series expanded into occurrences.
getUtilisation getUtilisation(from?: string | number | Date, to?: string | number | Date) => Promise<BmxSchedulerUtilisation[]> Each resource's utilisation between two times (default: the range on show).
goToDate goToDate(date: string | Date) => Promise<void> Shows the range around a date.
load load(url: string) => Promise<void> Loads { resources, events } as JSON, or bookings from an .ics file, from a URL.
loadICS loadICS(source: string | Blob, merge?: boolean) => Promise<number> Reads bookings from an .ics file (its text or a File), replacing them, or adding with merge.
next next() => Promise<void> The next range.
openEditor openEditor(id?: string) => Promise<void> Opens the editor on a booking (by id), or on a new one.
previous previous() => Promise<void> The previous range.
redo redo() => Promise<boolean>
removeEvent removeEvent(id: string) => Promise<boolean> Removes a booking (a whole series, for a repeating one).
scrollToTime scrollToTime(time: string | number | Date) => Promise<void> Scrolls the timeline to a time (09:00 on the range's first day, or an instant).
setFocus setFocus() => Promise<void> Moves focus to the chosen booking, or the first one on show.
toICS toICS(name?: string) => Promise<string> Every booking as an iCalendar (.ics) file's text.
today today() => Promise<void> The range with today in it, scrolled to now.
undo undo() => Promise<boolean>
updateEvent updateEvent(id: string, changes: Partial<BmxSchedulerEvent>) => Promise<boolean> Changes a booking's fields. False when there is no such booking or the change is refused.

CSS shadow parts

PartDescription
agenda the agenda list.
blocked time a resource is unavailable.
dialog the editor, the "which occurrences" question and the time finder.
event a booking. Also event-selected, event-clash, event-tentative, event-cancelled, event-recurring.
ghost a booking being dragged, or the time being dragged over to make one.
group a group's row. Also group-collapsed.
handle a booking's resize grip.
header the dates and hours across the top.
lane a row's timeline.
non-working time outside working hours.
now the line at the current time.
range-label the dates on show.
resource a resource's cell at the start of its row.
row a resource's row.
scroller the scrolling timeline.
status the status bar under the timeline.
tick one date or hour in it. Also tick-today.
tick-hour
toolbar the navigation, view and tool buttons.
toolbar-button one of them.
utilisation a resource's utilisation bar.

CSS custom properties

PropertyDescription
--bmx-scheduler-blocked-color The stripes over time a resource is unavailable.
--bmx-scheduler-border The lines between rows, days and hours.
--bmx-scheduler-clash-color The outline and mark of a clash.
--bmx-scheduler-event-background A booking without a colour of its own.
--bmx-scheduler-event-color Text on a booking.
--bmx-scheduler-event-radius A booking's corners.
--bmx-scheduler-header-background The dates and hours across the top, and the resource column's heading.
--bmx-scheduler-height How tall the timeline is (the toolbar and status bar add to it).
--bmx-scheduler-non-working-background Time outside working hours.
--bmx-scheduler-now-color The line at the current time.
--bmx-scheduler-resource-background The resource column.
--bmx-scheduler-today-background Today's column in the header.