Scheduler

Calendars on UI SDK createSpans: a week, a day or a room timeline where people draw, move and resize events with the pointer or arrows, overlapping events sharing lanes, a month view, a toolbar, an event dialog, and agent actions.

Calendars on UI SDK's createSpans: events people draw, move and resize with the pointer or the keyboard, on a week, a day, or a timeline of rooms.

pnpm dlx shadcn@latest add superzero11/uiset/scheduler

Parts

PartWhat it is
ScheduleTracks of events: days down a week (axis="y"), or rooms across a day (axis="x")
MonthViewA month on UI SDK's Calendar; each day lists its events; choosing a day opens it
SchedulerToolbarToday, previous and next, the range shown, the view, New event
EventDialogCreate or edit: title, day, start, end, where; Delete asks first
createScheduleControllerConnects EventDialog (or your code) to a Schedule: its rules and its focus

Use

tsx
const [schedule] = useState(createScheduleController)

<SchedulerToolbar title="Nov 16 – 22, 2026" view={view} onViewChange={setView} unit="week"
  onToday={…} onPrevious={…} onNext={…} onNewEvent={() => setOpen(draft)} />
<Schedule
  label="Team calendar"
  controller={schedule}
  tracks={days.map((d) => ({ id: d, label: longDate(d), short: shortDate(d) }))}
  events={events}
  onEventsChange={(next, { action, id }) => setEvents(next)}
  onEventOpen={setOpen}
  startHour={8}
  endHour={19}
  step={15}
  now={new Date()}
  timeZone="America/Los_Angeles"
  today={todayIso}
/>
<EventDialog schedule={schedule} startHour={8} endHour={19} step={15}
  event={open} tracks={tracks} onSave={save} onDelete={remove} onClose={() => setOpen(null)} />
  • An event is { id, track, start, end, title, color?, location? }; start and end are minutes from midnight, track is a day (an ISO date) or a room id.
  • overlap={false} refuses bookings that collide, for every kind of input. With overlaps allowed, events share the day's width in lanes (stackSpans).
  • onCreate(span) makes the event a draw or click creates; return null to refuse.
  • locked(event) keeps an event from changing, for people and agents.
  • Time zones. start and end are wall-clock minutes in the zone the schedule shows, and track is that zone's date. now is read in timeZone (the viewer's by default), so the current-time line and today agree. Convert stored instants to the zone at the edge; a 9:00 event stays at 9:00 across a daylight-saving change.
  • EventDialog with the controller keeps the schedule's rules (hours, steps, overlaps) and says why a time won't do, on the field that's wrong. Focus goes back to the saved event, or to the next event once one is deleted.
  • size is how long a click on an empty stretch makes an event. renderEvent replaces an event's face; timeLabel changes how times read (24-hour, other languages); MonthView takes locale and timeLabel too.
  • On touch screens a finger scrolls the schedule; a tap makes an event, and events drag.

Keys

KeysDoes
TabEach event, in time order
Up, Down (Left, Right on a timeline)Earlier or later by the step
Left, Right (Up, Down on a timeline)The previous or next day or room
Shift with those arrowsChange when it ends
Delete, BackspaceRemove it (the demo offers Undo)
Enter, SpaceOpen it (each event is a button)

New events by keyboard come from New event. Every move, resize and removal is announced. Delete hands focus to the next event, or to the schedule when none is left. Choosing a day in the month focuses that day's first event. In the month view each day is named with its events ("Wednesday, November 18, 2026, 2 events: Standup at 9:00 AM, Launch rehearsal at 3:00 PM").

For agents

addEvent(day, start, end, title), moveEvent(event, day, start), resizeEvent(event, end) and openEvent(event), with times as HH:MM and days by name ("Tuesday"). They keep the same rules a drag does, and a time that doesn't fit says why. Events are named by title or id; when two share a title, the error lists both with their ids. The snapshot lists each event as { value: id, label: "Standup, Monday, …" }. Deleting goes through the dialog's confirm (data-agent-confirm), so a person approves it; removeEvent stays on the API for your own code.