SuperScheduler is a React library for interactive resource planning: a fast, accessible timeline where your users drag, resize, zoom and undo, shaped around your business instead of the other way round.
Loads SuperScheduler Pro only when you ask for it.
Explore time
From the quarter to the quarter hour.
Zoom continuously with a pinch, Ctrl/⌘ and the wheel, or the buttons below. Headers, labels and detail adapt as the scale changes, and the minimap keeps the whole season in view.
01One time-per-pixel scale: zooming never re-renders React.
02Zoom levels you define, each with its own headers.
03The minimap draws whatever load your application computes.
Jump to a scaleQuarter · Month · Week · Day · Hours · 15 min
Loads SuperScheduler Pro only when you ask for it.
Work with resources
Teams, rooms and machines, arranged the way you think about them.
Resources can be a flat list, a tree several levels deep or a table with columns. Regroup the same people by team or by skill, filter as you type, and keep the timeline aligned.
1Tree rowsParents fold and unfold; children keep their own events.
2Row header columnsResizable columns read from your resource data.
3FiltersRows and events filter through callbacks you write.
Act with confidence
Move it. Read the callback. Undo it.
Every gesture reaches your code as a typed callback while it happens and after it lands. Refuse a move with a reason, confirm it with your server, or let it through. History is one import away.
Loads SuperScheduler Pro only when you ask for it.
1Drag a booking to another room or day.
2Try to drop one on the room under maintenance.
3Press Undo, or Ctrl/⌘ + Z inside the board.
Handler: act-handlers.ts+
act-handlers.tsts
/** * Booking-board handlers of the "act with confidence" demo. This file is shown on the page as * the code that runs: the library proposes, these handlers decide, React state persists. */import { type SuperScheduler } from'super-scheduler'importtype { SchedulerProps } from'super-scheduler'import { hitsMaintenance } from'../data/bookings'importtype { CallbackRecord } from'../callback-log'typeLog= (record:Omit<CallbackRecord,'seq'>) =>voidexportfunctioncreateBookingHandlers(log:Log, refusal:string):Partial<SchedulerProps> {return {// Visual and enforced: the library refuses drops on disabled cells.onBeforeCellRender: (args) => {const { resource,start,end } =args.cellif (hitsMaintenance(String(resource),start.value,end.value))args.cell.properties.disabled =true },// While dragging: explain the refusal next to the pointer.onEventMoving: (args) => {if (hitsMaintenance(String(args.resource),args.start.value,args.end.value)) {args.allowed =falseargs.message = refusal } },// Before the commit: the last word, e.g. after asking a server.onEventMove: (args) => {constrefused=hitsMaintenance(String(args.newResource),args.newStart.value,args.newEnd.value, )if (refused) args.preventDefault()log({ name:'onEventMove', args: {'e.id()':String(args.e.id()),'e.text()':args.e.text(), newResource:String(args.newResource), newStart:args.newStart.value, newEnd:args.newEnd.value, },...(refused ? { refused: refusal } : {}), }) },onEventMoved: (args) =>log({ name:'onEventMoved', args: {'e.id()':String(args.e.id()), newResource:String(args.newResource), newStart:args.newStart.value, newEnd:args.newEnd.value, }, }),onEventResized: (args) =>log({ name:'onEventResized', args: {'e.id()':String(args.e.id()), what:args.what, newStart:args.newStart.value, newEnd:args.newEnd.value, }, }), }}exportfunctionisBookingEvent( event:SuperScheduler.EventData,): event isSuperScheduler.EventData<{ client:string }> {return'client'in event &&typeofevent.client ==='string'}
It fits your product
Your data and your rules, on a timeline you don’t have to build.
A component, typed props and callbacks. The library owns rendering, gestures, virtualization and accessibility; your application owns data, validation and persistence.
SuperScheduler
Renders resources and events, virtualized in both directions.
Reports proposed changes and applies accepted ones.
Your application
Keeps state, validates, saves to the server.
src/BookingBoard.tsxtsx
import { useMemo, useState } from'react'import { SuperSchedulerComponent } from'super-scheduler'importtype { SchedulerEventsChangeArgs, SuperScheduler } from'super-scheduler'typeBooking=SuperScheduler.EventData<{ locked?:boolean }>exportfunctionBookingBoard({ rooms, initial,}: { rooms:SuperScheduler.ResourceData[] initial:Booking[]}) {// Your state is the source of truth; the scheduler gets a copy it may mutate.const [bookings,setBookings] =useState(initial)constevents=useMemo(() =>bookings.slice(), [bookings])return ( <SuperSchedulerComponentstartDate="2026-10-01"days={31}scale="Day"resources={rooms}events={events}allowEventOverlap={false}// While dragging: explain why a drop is not allowed.onEventMoving={(args) => {if ('locked'inargs.e.data &&args.e.data.locked ===true) {args.allowed =falseargs.message ='This booking is confirmed and locked.' } }}// Before the commit: ask your server, then accept or cancel.onEventMove={(args) => {args.async =truesaveBooking({ id:String(args.e.id()), resource:String(args.newResource), start:args.newStart.value, end:args.newEnd.value, }).then( () =>args.loaded(), () => {args.preventDefault()args.loaded() }, ) }}onEventsChange={({ events: next }:SchedulerEventsChangeArgs) =>setBookings([...next])} /> )}
SuperScheduler
Mounts React content in events and headers, retained and batched.
Your application
Decides what a booking or a row looks like.
src/RichPlanning.tsxtsx
import { SuperSchedulerComponent } from'super-scheduler/react-render'importtype { SuperScheduler } from'super-scheduler'import'super-scheduler/styles.css'interfaceStay { guest:string nights:number paid:boolean}/** Event data comes back untyped from the engine: read your fields explicitly. */functionreadStay(data:object):Stay|null {if (!('guest'in data) ||!('nights'in data) ||!('paid'in data)) returnnullconst { guest,nights,paid } = datareturntypeof guest ==='string'&&typeof nights ==='number'&&typeof paid ==='boolean'? { guest, nights, paid }:null}exportfunctionRichPlanning({ rooms, stays,}: { rooms:SuperScheduler.ResourceData[] stays:SuperScheduler.EventData<Stay>[]}) {return ( <SuperSchedulerComponentstartDate="2026-10-01"days={30}scale="Day"resources={rooms}events={stays}// React content inside each event; width is quantized, so it re-renders rarely.renderEvent={({ e, width }) => {conststay=readStay(e.data)if (stay ===null) returne.text()return ( <spanclassName="stay"> <strong>{stay.guest}</strong> {width >=120? <small>{stay.nights} nights</small> :null} {stay.paid ?null: <spanclassName="stay__due">Due</span>} </span> ) }}eventHover={{ pin:'click',render: ({ e, close }) => {conststay=readStay(e.data)return stay ===null?null: <StayCardstay={stay} onClose={close} /> }, }} /> )}functionStayCard({ stay, onClose }: { stay:Stay; onClose: () =>void }) {return ( <sectionaria-label={stay.guest}> <h3>{stay.guest}</h3> <p> {stay.nights} nights · {stay.paid ?'Paid':'Payment due'} </p> <buttontype="button"onClick={onClose}> Close </button> </section> )}
SuperScheduler
Exposes --super-scheduler-* tokens and follows your dark mode.
Your application
Chooses colors, radius and density.
theme.csscss
/* planning.css: the scheduler follows your tokens and your dark mode. */.planning { --super-scheduler-accent-rgb:25 97 68; --super-scheduler-surface:#fcfaf6; --super-scheduler-border:#ddd8d2; --super-scheduler-event-radius:7px; --super-scheduler-today-bg:rgb(25 97 68 / 0.06); --super-scheduler-selection:rgb(25 97 68 / 0.12);font-family:'Geist', system-ui, sans-serif;}[data-theme='dark'] .planning { --super-scheduler-accent-rgb:110 214 166; --super-scheduler-surface:#16181c; --super-scheduler-border:#2e3134;}.planning.super-scheduler__event[data-selected='true'] {outline:2px solid rgb(var(--super-scheduler-accent-rgb));}
Dress it like your product
Real tokens, real contrast, copy the CSS.
These controls change the --super-scheduler-* custom properties the library reads. The contrast check uses WCAG 2 ratios; the CSS below is what you paste into your stylesheet.
Is it a React component or a framework-agnostic engine?+
Both. SuperSchedulerComponent is the React wrapper for React 18 and 19; underneath, a DOM engine handles scrolling, zoom and gestures without React renders. You can also mount it imperatively with new SuperScheduler.Scheduler(element, options).
Declarations ship with the package. Options, callbacks and control methods are typed, and event data is generic: EventData<YourFields> keeps your own fields typed in handlers and render hooks.
Yes. Colors, radius, density and dark mode come from documented CSS custom properties; classNames and styles reach individual parts; React render slots replace the content of events, rows and headers.
Your application. The scheduler shows and edits what you give it and reports every change through callbacks. Persistence, permissions and the final word on conflicts belong to your backend; the library helps with overlap prevention, blocked cells and refusals with a reason.
As a private, versioned .tgz over HTTPS that npm installs like any URL dependency. The download key appears in your manifest and lockfile, so treat it as a secret. Lite is a regular public npm package.
Can users work with the keyboard and screen readers?+
Pro has a full keyboard mode: arrows move focus, Enter activates, Alt+arrows move events, and changes are announced to assistive technology in English, Spanish, French, German and five more languages. Lite supports keyboard navigation too.
Dates are civil wall-clock values, deliberately free of time zones. Convert zoned instants and expand recurrences in your application before handing events to the scheduler.