Pan and zoom

Moving through a plan with the gestures of any scrolling surface: drag, wheel, Ctrl with the wheel, and a pinch. Only the view moves; the plan stays as it is.

import { useSchedule } from "@umriss-ui/schedule";

Dragging the background pans through time and through the lanes. The wheel scrolls the lanes and hands the scroll back to the page at their end; Shift with the wheel pans through time; Ctrl or ⌘ with the wheel, or a pinch, zooms around the pointer.

No intent is raised: pan and zoom change the visible span and nothing else (ADR-0001). The span is part of the schedule's view, which onViewChange reports at most once per frame - see View and Linked schedules.

Examples

Move through the plan

Drag, wheel, Ctrl with the wheel and a pinch need nothing switched on; zoomLimits only bounds the span, here two hours to two days.

import { useSchedule } from "@umriss-ui/schedule";
import type { Subtask, Task } from "@umriss-ui/schedule";

/* Without `zoomLimits` the span may run from an hour to twenty-eight days. */

const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
const min = (n: number) => n * 60_000;
const HOUR = 60 * 60_000;

const TASKS: Task[] = [
  { id: "release", name: "Release 4.12", color: "light-dark(#2563eb, #6b9bff)" },
  { id: "inc-1048", name: "INC-1048 follow-up", color: "light-dark(#c2410c, #f08a52)" },
];

const ENGINEERS = [
  { id: "priya", label: "Priya Raman" },
  { id: "jonas", label: "Jonas Keller" },
  { id: "ada", label: "Ada Mwangi" },
];

const WORK: Subtask[] = [
  { id: "build", task: "release", lane: "priya", from: at(6), to: at(7), leadIn: min(15), leadOut: min(10) },
  { id: "rollout", task: "release", lane: "jonas", from: at(8), to: at(10, 30), leadIn: min(30), leadOut: min(15) },
  { id: "timeline", task: "inc-1048", lane: "ada", from: at(6, 30), to: at(8), leadIn: min(30), leadOut: min(15) },
  { id: "postmortem", task: "inc-1048", lane: "jonas", from: at(12), to: at(13, 30), leadIn: min(15) },
];

export default function PanAndZoom() {
  const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(5, 30), at(18)] } });
  return (
    <Schedule
      ariaLabel="A plan to pan and zoom"
      height={196}
      zoomLimits={{ min: 2 * HOUR, max: 48 * HOUR }}
    >
      {ENGINEERS.map((engineer) => (
        <Lane key={engineer.id} id={engineer.id} label={engineer.label} />
      ))}
      <Subtasks data={WORK} tasks={TASKS} />
    </Schedule>
  );
}

Keyboard

Key Action
Home / End The first or last subtask on the lane; the view pans to bring it in.
PageUp / PageDown Along the lane by about a tenth of the visible span, the view following.

The keys of First schedule apply here.

Props on this page

From ScheduleProps — the full table stands on First schedule.

NameTypeDefaultDescription
heightnumber400Height in CSS pixels, both bands included. The width is the host's.
zoomLimitsZoomLimitsone hour to 28 daysThe narrowest and widest time span zoom may reach, in milliseconds.
childrenReactNode—Lane, Dependencies and Subtasks, in the order they are to stand and be drawn.

Accessibility

NameTypeDefaultDescription
ariaLabelrequiredstring—What the schedule shows, for a screen reader: "Plan of week 38".

From LaneProps — the full table stands on Lanes.

NameTypeDefaultDescription
idrequiredstring—The identity subtasks name in their lane.
labelReactNodeidWhat the lane header says - the person, vehicle or room. Real text, read by a screen reader.

From SubtasksProps — the full table stands on Subtasks.

NameTypeDefaultDescription
datarequiredreadonly Subtask[]—The subtasks, as the caller holds them. The schedule never changes one.
tasksrequiredreadonly Task[]—The tasks the subtasks belong to - their identity and their colour. A subtask whose task is not among them is drawn in a muted colour.

From Subtask — the full table stands on Subtasks.

NameTypeDefaultDescription
idrequiredstring—The caller's identity of the subtask.
taskrequiredstring—The id of the task it belongs to.
lanerequiredstring—The id of the lane it sits on.
fromrequirednumber—Start of the main time.
torequirednumber—End of the main time.
leadInnumber—Duration of the lead-in before the main time; none is zero.
leadOutnumber—Duration of the lead-out after the main time; none is zero.

From Task — the full table stands on Subtasks.

NameTypeDefaultDescription
idrequiredstring—The caller's identity of the task.
colorrequiredstring—Any CSS colour - a literal, a token, light-dark(…). The colour means what the caller's domain needs it to mean.
namestring—What the task is called, where the schedule names it.

From ScheduleOptions — the full table stands on View.

NameTypeDefaultDescription
initialViewScheduleView—The view to start from, read once when the hook first runs; a different value on a later render is ignored, as React ignores a changed defaultValue. To go to a view later, call setView. The schedule remembers none: where a view is kept is the application's decision.

From ScheduleView — the full table stands on View.

NameTypeDefaultDescription
domainreadonly [number, number]—The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within zoomLimits.

Every page of @umriss-ui/schedule

Getting started

Plan

Time

Reading

Editing

Measuring

API index