04core concepts
Resource timeline
Rooms, staff or machines as rows along a horizontal time axis — drag to book, drag across rows to reassign, drag an edge to resize.
view="timeline" turns the grid ninety degrees: time runs left to right
instead of top to bottom, and the rows are whatever you name them — rooms,
staff, trucks, machines — instead of days. It is the seventh view shape, and
the odd one out: it doesn't draw the all-day lane, it isn't part of the
responsive collapse (there's no
narrower n-day span to fall back to, so a timeline just scrolls), and it
keeps its own visible-span model instead of view.days.
Rows: the lanes prop
lanes is empty by default — nothing renders without it. Passing it also adds
a Timeline entry to the stock view switcher (and sets useKloq().timeline for
your own). Each one is a
Lane:
parentId is on the type today but does nothing yet — see
what's not here. How an event maps onto a lane is a
separate question, covered in Resources & lanes.
The visible span: timelineWindow
A timeline has no "week" or "day off" the anchor — how much time it shows, and how finely, is its own prop:
There are two modes, and which one you're in changes how navigation behaves:
- Derived (the default) — omit both
startandend. The window is computed off the calendar's own anchor date:startis the anchor's local midnight plusstartOffsetMinutes,endisstartplusspanMinutes. This is what makes ‹ › and "Today" move the window with zero extra wiring —stepAnchormoves the anchor, the anchor derives the window. - Controlled — pass both
startandend. They're honoured exactly, andresolveWindownever recomputes them from the anchor again. The toolbar's ‹ › still moves the calendar's anchor date (nothing stops that), but the visible span no longer follows it — moving the window becomes your job, typically by deriving a newstart/endin your ownonDateChangeand passing it back in as new props.
start/end are all-or-nothing: pass only one and the window derives off
the anchor as if you'd passed neither, the same fallback rule
slotMinutes/pxPerSlot each get independently.
A worked room-booking config — a bookable working day, half-hour slots:
And a roster pinned to a fixed span — controlled, because a shift roster is about dates, not the calendar's current anchor. Day-sized slots turn it into a fortnight planner:
See the full example for the first shape end to end; Shift scheduler is the controlled shape too, pinning the current calendar week with hour slots.
ticksOf positions ticks and derives their labels off the local clock,
so a window that spans a daylight-saving transition computes against a 23-
or 25-hour day. Transitions land in the small hours, so a window inside
the working day never sees one; a full-day, week or longer window does,
twice a year — expect the ticks after the boundary to drift by the shifted
hour.
Try it
liveRoom booking
uses
Gestures
Every gesture from the calendar's own drag engine exists here, remapped onto lane + minute instead of day + minute:
- Drag to create — drag on empty canvas to draw a new booking, snapped to
slotMinutes. - Drag to move — grab a bar and drag it. Drag across rows and the drop
reassigns it: the engine calls your
LaneAdapter'sassign(event, newLaneId)for you, the same inverselaneOfdescribes. - Resize — drag either edge to change the start or end independently.
- Double-click — creates a default-duration booking at the clicked lane and time, same as the calendar's double-click quick-create.
Constraints apply unchanged: minDate/maxDate,
isSlotDisabled and canDrop all run against timeline gestures exactly as
they do against the calendar's, including the origin exemption — a
booking already sitting across refused time can still be dragged, so long as
the drag doesn't newly occupy more refused time. See
Constraints for what "newly
occupies" means, and its timeline gesture names
if you're writing a canDrop that branches on which surface asked.
A booking that crosses midnight is drawn as a single continuous bar — the
timeline has no day columns to slice it against, unlike the calendar's own
cross-midnight segmentation. An all-day booking is a bar labelled "All day",
and one marked busy: false draws hatched, as it does in the calendar.
Conflicts are judged per lane: two bookings clash only when they share a row.
The header and the rows share one scroll container, so the tick strip stays pinned to the top and the resource gutter to the left while the canvas scrolls. The gutter is 160px wide, and 112px once the timeline's own box is narrower than 480px.
Keyboard
The same keys as the calendar, remapped onto lanes:
- Arrows walk the bars —
←/→along a lane,↑/↓to the lane above or below. With nothing selected, an up or down arrow starts at the first bar. Hold⇧to extend the selection. ⌥+ arrows move the bar: one slot earlier or later, or onto the neighbouring lane (the same reassignment a cross-row drag makes).⌥⇧+←/→resize the end;⌥⇧⌘resizes the start.1–5recolour the selection, and the create key (cunless the keymap rebinds it) books a default-length slot on the current lane.Enter,Escape,Deleteand⌘A/⌘C/⌘X/⌘Dact on any bar in the window, on any of its days.
Zoom
Zoom is the calendar's existing control (the same ladder hourHeight scales
through, useKloqSettings().zoom) — it scales pxPerSlot, never
slotMinutes or the span the window covers. Zooming in makes 8:00–18:00 wider
on screen; it never shows you more or less of the day.
Row height and the overflow cap
Lane.height is more than a row's pixel height — it's also the cap on how
many overlapping bookings that row draws before the rest collapse into a
"+N more" popover, the same treatment the calendar's all-day lane uses for
an over-full day. The cap is height ÷ bar height, floored, with a floor of
one row. The default row height caps at 3 overlapping bookings; a room
or shift that's routinely double- or
triple-booked wants a taller Lane.height, not a different prop — there is
deliberately no separate "max overlaps" knob.
Slots
Four new component slots join the six the
calendar already has: TimelineBlock, ResourceGutter, TimelineHeader,
TimelineRow. Their contracts are on the Slots page.
What is not here
This ships the surface, not a project-management tool:
- No virtualization. Every lane and every bar renders. It hasn't been exercised past a modest row count, and a long resource list is likely to perform poorly — plan around it rather than assuming it scales the way a virtualized grid would.
- No resource grouping or trees.
Lane.parentIdexists on the type for forward compatibility and does nothing today; lanes render as one flat list in the order you pass them. - No dependency arrows, critical path, or baselines. This is a booking grid, not a Gantt chart.
- Three keyboard gaps.
⌥+arrow moves or reassigns the lead bar only, not the whole selection; all-day and read-only bars don't move from the keyboard; and⌘Vpastes at the lead bar's time (the window's first day when there is none) on the copied bookings' own lanes, not on the lane the lead sits on.