# Quick start with Lite

> Install super-scheduler-lite from npm and render a read-only daily resource timeline in React: options and defaults, click callbacks, imperative control and limits.

Source: https://superscheduler.org/en/docs/quick-start-lite/
Reviewed: 2026-10-07

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.

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](https://superscheduler.org/en/docs/install-pro/) and [Migrate from Lite to Pro](https://superscheduler.org/en/docs/migrate-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
```sh
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
```tsx
// src/Planning.tsx
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.

> **Tip:**
> Keep `resources` and `events` stable between renders: module constants, state, or `useMemo`. The component compares props by identity and only sends changed ones to the control, so a new array literal on every render rebuilds the timeline every time.

## 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):

```css
.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](#rejects)).

| Option | Type | Default | Notes |
|---|---|---|---|
| `startDate` | ISO string or `SuperScheduler.Date` | Today | The first day; a time of day is ignored |
| `days` | positive integer | `31` | Number of day columns |
| `scale` | `'Day'` | `'Day'` | The only accepted value |
| `cellWidth` | number (px) | `64` | Width of one day |
| `height` | number (px) | `400` | Total height of the scrolling box, header included |
| `rowHeaderWidth` | number (px) | `160` | Width of the resource name column |
| `rowMinHeight` | number (px) | `40` | Rows grow when overlapping events stack |
| `eventHeight` | number (px) | `26` | Height of one event line |
| `resources` | `ResourceData[]` | `[]` | `{ id, name }`, flat |
| `events` | `EventData[]` | `[]` | See [Event fields](#event-fields) |
| `locale` | string | `'en-us'` | Day labels in the header, such as `es-es` or `de-de` |
| `ariaLabel` | string | `'Resource schedule'` | Accessible name of the grid, also shown in the top-left corner |
| `emptyState` | string | `'No resources'` | Text shown when `resources` is empty |
| `onEventClick` | function | none | See [Respond to clicks](#clicks) |
| `onTimeRangeClick` | function | none | See [Respond to clicks](#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:

| Field | Required | Meaning |
|---|---|---|
| `id` | yes | String or finite number, unique among events |
| `resource` | yes | The `id` of the row it belongs to, with the same type |
| `start`, `end` | yes | ISO strings (`2026-10-02` or `2026-10-02T14:00:00`, seconds included) or `SuperScheduler.Date`; `end` is exclusive |
| `text` | yes | The label, rendered as text (never as HTML) |
| `backColor`, `fontColor` | no | Any CSS color |
| `cssClass` | no | Extra class names on the event button |
| `toolTip` | no | Native tooltip; defaults to `text` |
| `tags` | no | Any 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](https://superscheduler.org/en/docs/resources-events-intervals/) 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`.

```tsx
// src/PlanningWithDetails.tsx
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.

> **Behavior:**
> Lite's callback arguments are smaller than Pro's: `e` exposes only `data`. In Pro, `args.e` is a `SuperScheduler.Event` wrapper with methods such as `id()` and `start()`. Keep handler logic on `e.data` fields if you plan to migrate.

## 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).

```tsx
// src/NavigablePlanning.tsx
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:

| Member | What 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:

```ts
// src/mount-planning.ts
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.

> **Limitation:**
> Lite has no editing, hour or minute cells, zoom, infinite scrolling, resource trees, frozen or split rows, links, selection, conflict highlighting, minimap, panes, history, saved views, range loading or React render slots. The code for them is not in the package. Extra fields on your event objects are kept but enable nothing.

## Next steps
- Understand the data rules shared by both editions: [Resources, events and intervals](https://superscheduler.org/en/docs/resources-events-intervals/).
- Embed the component correctly in a larger React app: [React integration](https://superscheduler.org/en/docs/react-integration/).
- See what an editable planning looks like in the [examples](https://superscheduler.org/en/examples/), all built with Pro.
- When you need editing: [Migrate from Lite to Pro](https://superscheduler.org/en/docs/migrate-lite-to-pro/).
