Skip to content
SuperScheduler

Start hereApplies toSuperScheduler Lite

Quick start with Lite

Run npm install super-scheduler-lite, import SuperSchedulerComponent and super-scheduler-lite/styles.css, and pass startDate, days, resources and events. Lite renders a read-only, virtualized timeline with one cell per day, reports clicks through onEventClick and onTimeRangeClick, and throws an error for any option it does not implement.

Verified against v0.1.0 · reviewed October 7, 2026.md

SuperScheduler Lite is the public, read-only edition: one row per resource, one column per day, events as bars, clicks reported to your code. It is the fastest way to put an occupancy or availability chart in a React application. This guide takes you from an empty project to a working timeline, then covers every option, the callbacks, the imperative API and what Lite deliberately rejects.

If you need dragging, resizing, hours and minutes, zoom or resource trees, those belong to Pro: see Install SuperScheduler Pro and Migrate from Lite to Pro.

Requirements

  • React 18.2 or later, or React 19. React is a peer dependency, so Lite uses your application's copy.
  • A bundler or framework that understands ES modules or CommonJS (Vite, Next.js, webpack, Parcel and similar). Both formats and their TypeScript declarations ship in the package.
  • A browser environment to render. The package can be imported during server rendering; the timeline itself is built in the browser when the component mounts.

Install the package

shsh
npm install super-scheduler-lite react react-dom

react-dom is listed because you render with it, not because Lite imports it.

Render a first timeline

src/Planning.tsxtsx
import { SuperSchedulerComponent } from 'super-scheduler-lite'
import type { SuperScheduler } from 'super-scheduler-lite'
import 'super-scheduler-lite/styles.css'

// Module-level arrays keep the same identity on every render, so React never re-applies them.
const ROOMS: SuperScheduler.ResourceData[] = [
  { id: 'r101', name: 'Room 101' },
  { id: 'r102', name: 'Room 102' },
  { id: 'r103', name: 'Room 103' },
]

const BOOKINGS: SuperScheduler.EventData[] = [
  // Date-only values: the bar covers 2, 3 and 4 October (the end is exclusive).
  { id: 1, resource: 'r101', start: '2026-10-02', end: '2026-10-05', text: 'Booking 1042' },
  // Overlaps the first booking on the same row: Lite stacks it on a second line.
  {
    id: 2,
    resource: 'r101',
    start: '2026-10-04',
    end: '2026-10-07',
    text: 'Booking 1043',
    backColor: '#dbeafe',
  },
  // Times are kept: the bar starts at 14:00 and ends at 11:00, inside the day cells.
  {
    id: 3,
    resource: 'r103',
    start: '2026-10-06T14:00:00',
    end: '2026-10-09T11:00:00',
    text: 'Booking 1051',
  },
]

export function Planning() {
  return (
    <SuperSchedulerComponent startDate="2026-10-01" days={31} resources={ROOMS} events={BOOKINGS} />
  )
}

You should see a 400-pixel-tall grid with a header of day labels (1 Oct, 2 Oct, …), three room rows and three bars. Booking 1042 covers 2, 3 and 4 October: the end date is exclusive, so a stay that ends on 2026-10-05 is gone at midnight of the 5th. Booking 1043 overlaps it, so Room 101 grows to two lines and stacks both bars. Booking 1051 starts at 14:00 on the 6th and ends at 11:00 on the 9th: Lite places bars at their exact times inside the day cells.

Scroll the grid in any direction. Only the rows, days and events in view exist in the DOM, and scrolling never causes a React render, whatever the size of your data.

Import the styles

Import super-scheduler-lite/styles.css once, typically in your entry file or root layout. The rules live in a CSS cascade layer named super-scheduler, so any unlayered rule in your own stylesheet overrides them without !important.

The root element has the class super-scheduler-lite and six custom properties. Override them on that class (not on a distant ancestor, because the root declares its own values):

csscss
.super-scheduler-lite {
  --super-scheduler-background: #ffffff;
  --super-scheduler-text: #18212f;
  --super-scheduler-border: #dce3ed;
  --super-scheduler-header: #f4f7fb;
  --super-scheduler-event: #d7e8fa;
  --super-scheduler-focus: #005cbf;
}

/* A dark theme driven by your own class on <html>. */
.dark .super-scheduler-lite {
  --super-scheduler-background: #121518;
  --super-scheduler-text: #f4f4f5;
  --super-scheduler-border: #2b3139;
  --super-scheduler-header: #1b1f24;
  --super-scheduler-event: #1f3a5c;
}

Lite sets its own font (13 px system UI) and fills the width of its parent. Per-event colors come from the data (backColor, fontColor) or from a cssClass you style yourself.

Options and defaults

Every option Lite accepts is in this table. Anything else throws (see What Lite rejects).

OptionTypeDefaultNotes
startDateISO string or SuperScheduler.DateTodayThe first day; a time of day is ignored
dayspositive integer31Number of day columns
scale'Day''Day'The only accepted value
cellWidthnumber (px)64Width of one day
heightnumber (px)400Total height of the scrolling box, header included
rowHeaderWidthnumber (px)160Width of the resource name column
rowMinHeightnumber (px)40Rows grow when overlapping events stack
eventHeightnumber (px)26Height of one event line
resourcesResourceData[][]{ id, name }, flat
eventsEventData[][]See Event fields
localestring'en-us'Day labels in the header, such as es-es or de-de
ariaLabelstring'Resource schedule'Accessible name of the grid, also shown in the top-left corner
emptyStatestring'No resources'Text shown when resources is empty
onEventClickfunctionnoneSee Respond to clicks
onTimeRangeClickfunctionnoneSee Respond to clicks

Numeric options must be positive and finite, and days must be an integer.

Event and resource fields

A resource is { id, name }. An event has five required fields and five optional ones:

FieldRequiredMeaning
idyesString or finite number, unique among events
resourceyesThe id of the row it belongs to, with the same type
start, endyesISO strings (2026-10-02 or 2026-10-02T14:00:00, seconds included) or SuperScheduler.Date; end is exclusive
textyesThe label, rendered as text (never as HTML)
backColor, fontColornoAny CSS color
cssClassnoExtra class names on the event button
toolTipnoNative tooltip; defaults to text
tagsnoAny value you want back in onEventClick

Ids are compared strictly: 1 and '1' are different ids, so an event with resource: '101' does not appear on a row with id: 101. Dates are civil wall-clock values with no time zone; the data model guide explains the rules, which are the same in both editions.

Respond to clicks

Lite reports two interactions. onEventClick receives { control, e, originalEvent }, where e.data is your event object. onTimeRangeClick receives { control, start, end, resource, originalEvent } for a click on an empty day cell; start is that day at midnight and end the next midnight, both as SuperScheduler.Date.

src/PlanningWithDetails.tsxtsx
import { useCallback, useState } from 'react'
import { SuperSchedulerComponent } from 'super-scheduler-lite'
import type {
  SchedulerEventClickArgs,
  SchedulerTimeRangeClickArgs,
  SuperScheduler,
} from 'super-scheduler-lite'

interface PlanningProps {
  readonly rooms: SuperScheduler.ResourceData[]
  readonly bookings: SuperScheduler.EventData[]
}

export function PlanningWithDetails({ rooms, bookings }: PlanningProps) {
  const [detail, setDetail] = useState('Select a booking or a free day.')

  // Stable callbacks: a new function per render would be sent to the control on every render.
  const onEventClick = useCallback((args: SchedulerEventClickArgs) => {
    // Lite hands you the event's own data object, including `tags`.
    setDetail(`${args.e.data.text} (id ${String(args.e.data.id)})`)
  }, [])

  const onTimeRangeClick = useCallback((args: SchedulerTimeRangeClickArgs) => {
    // One day cell: `end` is the next midnight. Enter and Space on the active cell also land here.
    setDetail(`Free cell: ${String(args.resource)} on ${args.start.toString('d MMMM yyyy')}`)
  }, [])

  return (
    <>
      <p aria-live="polite">{detail}</p>
      <SuperSchedulerComponent
        startDate="2026-10-01"
        days={31}
        resources={rooms}
        events={bookings}
        onEventClick={onEventClick}
        onTimeRangeClick={onTimeRangeClick}
      />
    </>
  )
}

You should see the paragraph change when you click a booking or a free cell. The same callbacks run from the keyboard: Tab focuses the grid, the arrow keys move the active cell, and Enter or Space on it calls onTimeRangeClick; events are buttons, so Enter on a focused event calls onEventClick. originalEvent is the DOM event behind the call: the KeyboardEvent when Enter or Space activated a cell, a click event otherwise.

Control the timeline from code

The React component creates a control when it mounts and disposes it when it unmounts. Reach it through ref.current.control on the component, or with the controlRef prop (a ref object or a callback; Lite sets it to null on unmount).

src/NavigablePlanning.tsxtsx
import { useRef } from 'react'
import { SuperScheduler, SuperSchedulerComponent } from 'super-scheduler-lite'

interface PlanningProps {
  readonly rooms: SuperScheduler.ResourceData[]
  readonly bookings: SuperScheduler.EventData[]
}

export function NavigablePlanning({ rooms, bookings }: PlanningProps) {
  // Lite sets `current` after mount and clears it on unmount.
  const controlRef = useRef<SuperScheduler.Scheduler | null>(null)

  const goToToday = () => controlRef.current?.scrollTo(SuperScheduler.Date.today())
  const findRoom = (id: SuperScheduler.ResourceData['id']) =>
    controlRef.current?.scrollToResource(id)
  const logRange = () => {
    const control = controlRef.current
    if (control !== null)
      console.info(`${control.visibleStart().value} to ${control.visibleEnd().value}`)
  }

  return (
    <>
      <div role="toolbar" aria-label="Planning navigation">
        <button type="button" onClick={goToToday}>
          Today
        </button>
        <button type="button" onClick={() => findRoom('r310')}>
          Room 310
        </button>
        <button type="button" onClick={logRange}>
          Visible range
        </button>
      </div>
      <SuperSchedulerComponent
        controlRef={controlRef}
        startDate="2026-10-01"
        days={92}
        height={520}
        resources={rooms}
        events={bookings}
      />
    </>
  )
}

The Lite control has eight members:

MemberWhat it does
update(options)Merges options into the current ones and redraws. An explicit undefined restores a default
scrollTo(date)Scrolls so date is at the left edge
scrollToResource(id)Scrolls so that row is at the top
visibleStart(), visibleEnd()The dates at the left and right edges of the scrolled view
disposed()Whether dispose() has run
dispose()Removes the DOM, listeners and observers and releases the data
init()Builds the DOM; the React component calls it for you

Without React, create the control on an element you own:

src/mount-planning.tsts
import { SuperScheduler } from 'super-scheduler-lite'
import 'super-scheduler-lite/styles.css'

/** Mounts a read-only planning into `host` without React and returns its teardown. */
export function mountPlanning(host: HTMLElement): () => void {
  const control = new SuperScheduler.Scheduler(host, {
    startDate: '2026-10-01',
    days: 31,
    resources: [
      { id: 'r101', name: 'Room 101' },
      { id: 'r102', name: 'Room 102' },
    ],
    events: [
      { id: 1, resource: 'r101', start: '2026-10-02', end: '2026-10-05', text: 'Booking 1042' },
    ],
    onEventClick: ({ e }) => console.info('booking', e.data.id),
  })
  control.init()

  // update() merges with the current options; an explicit undefined restores a default.
  control.update({ days: 62, cellWidth: 48 })
  control.scrollTo('2026-10-15')

  return () => control.dispose()
}

Update the data

The component sends only changed props to control.update(), comparing them by identity. To change the data, pass a new array: setEvents([...events, next]) works, while events.push(next) on the same array does not reach the timeline until you call control.update() yourself. Updates that only change onEventClick or onTimeRangeClick swap the callbacks without redrawing.

What Lite rejects

Lite validates its input and throws instead of ignoring what it cannot do, so a misconfiguration surfaces during development rather than as a half-working screen:

  • An option outside the table above, including Pro options such as allowEventOverlap or zoomLevels, even when passed from plain JavaScript: SuperScheduler Lite: unsupported option "zoomLevels".
  • scale other than 'Day'.
  • A resource with children, frozen, split or columns (resource children requires Pro).
  • Duplicate resource ids, ids that are not strings or finite numbers, and events whose end is before their start.
  • Non-positive or non-finite sizes, and a fractional days.

When update() throws, the previous configuration stays on screen and usable. In React the error is thrown while the component commits the new props, so an error boundary above it catches it.

Next steps