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

<Kloq view="timeline" lanes={lanes} />

lanes is empty by default — nothing renders without it. Each one is a Lane:

interface Lane {
  id: string;
  label: string;
  sublabel?: string; // an optional second line, e.g. "2nd floor · 8 seats"
  height?: number;   // px; see "Row height and the overflow cap" below
  parentId?: string; // reserved for grouping — ignored in v1
  data?: unknown;    // handed back to a custom ResourceGutter untouched
}

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:

interface TimelineWindowConfig {
  start?: Date;
  end?: Date;
  slotMinutes?: number;   // the snap and tick unit. Default 30.
  pxPerSlot?: number;     // px per slot, before zoom. Default 40.
  startOffsetMinutes?: number; // derived mode only. Default 480 (8:00 AM).
  spanMinutes?: number;        // derived mode only. Default 600 (10 hours).
}

There are two modes, and which one you're in changes how navigation behaves:

  • Derived (the default) — omit both start and end. The window is computed off the calendar's own anchor date: start is the anchor's local midnight plus startOffsetMinutes, end is start plus spanMinutes. This is what makes ‹ › and "Today" move the window with zero extra wiring — stepAnchor moves the anchor, the anchor derives the window.
  • Controlled — pass both start and end. They're honoured exactly, and resolveWindow never 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 new start/end in your own onDateChange and 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:

<Kloq
  view="timeline"
  lanes={rooms}
  timelineWindow={{
    startOffsetMinutes: 8 * 60, // 8:00 AM
    spanMinutes: 10 * 60,       // 8:00 AM – 6:00 PM
    slotMinutes: 30,
  }}
/>

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:

<Kloq
  view="timeline"
  lanes={staff}
  timelineWindow={{
    start: periodStart,
    end: periodEnd, // any pair of Dates — 14 days here
    slotMinutes: 1440, // one slot per day
    pxPerSlot: 96,
  }}
/>

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.

Try it

liveRoom bookingviewlaneslaneAdapterbyResourceId
loading

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's assign(event, newLaneId) for you, the same inverse laneOf describes.
  • 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.

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.

{ id: "boardroom", label: "Boardroom", height: 132 } // taller row, higher cap: height ÷ bar height, floored

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.parentId exists 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.
  • Keyboard support is partial. ⌘K, undo/redo and page up/down work, and Tab reaches bars through the shell's usual roving tabindex. Copy/cut/duplicate/delete work for bars on the window's anchor date only: the selection layer still reasons about the calendar's day-column projection, which for a timeline covers just that one day, so on a multi-day window a bar on any other day can't hold a selection. Arrow-key navigation between bars, +arrow move/resize, select-all, Escape/Enter on a selected bar, the 1–5 recolour keys and n-to-create don't work — same cause, no lane-aware equivalent yet. Drag, resize and create gestures are unaffected on any day; click-to-select (and the panel it opens) shares the selection layer's anchor-date limit.

on this page