Overlap

Two subtasks claiming one lane at the same time (also called a double booking or conflict): both drawn, offset and marked, never packed into sub-lanes.

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

The later bar is offset a few pixels and edged, and the shared time is marked across the lane. Lead-in and lead-out occupy the lane too, so a lead-out meeting the next lead-in is an overlap. The offset stops at three levels and never leaks into the lane below.

Examples

A person booked twice

Both bars stay where their times put them: the later one is offset and edged, and the shared time is marked across the lane.

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

const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();

const PROJECTS: Task[] = [
  { id: "booking", name: "Booking app", color: "light-dark(#0d9488, #3cc7b8)" },
  { id: "intranet", name: "Intranet", color: "light-dark(#be185d, #f06aa6)" },
];

const WORK: Subtask[] = [
  { id: "calendar-sync", task: "booking", lane: "kofi", from: at(9), to: at(12, 30) },
  { id: "news-feed", task: "intranet", lane: "kofi", from: at(11), to: at(15) },
  { id: "reminders", task: "booking", lane: "hana", from: at(9), to: at(13) },
];

export default function DoubleBooking() {
  const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(8), at(16)] } });
  return (
    <Schedule ariaLabel="Kofi booked on two projects at once" height={150}>
      <Lane id="kofi" label="Kofi Mensah" />
      <Lane id="hana" label="Hana Sato" />
      <Subtasks data={WORK} tasks={PROJECTS} />
    </Schedule>
  );
}

Lead-out meeting lead-in

Unloading and the next loading occupy the vehicle too: where a leadOut runs into the next leadIn, that is an overlap.

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

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

const TOURS: Task[] = [
  { id: "t-04", name: "T-04 Ringway", color: "light-dark(#0d9488, #3cc7b8)" },
  { id: "t-06", name: "T-06 Riverside", color: "light-dark(#be185d, #f06aa6)" },
];

const LEGS: Subtask[] = [
  { id: "t-04-1", task: "t-04", lane: "fp-402", from: at(7), to: at(8, 30), leadOut: min(30) },
  { id: "t-06-1", task: "t-06", lane: "fp-402", from: at(9, 15), to: at(10, 45), leadIn: min(30) },
];

export default function LeadOutMeetsLeadIn() {
  const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(6, 30), at(11, 30)] } });
  return (
    <Schedule ariaLabel="Two rounds of one van, touching at unloading and loading" height={110}>
      <Lane id="fp-402" label="Van FP 402 R" />
      <Subtasks data={LEGS} tasks={TOURS} />
    </Schedule>
  );
}

Three at once

Each further bar steps down a little, three levels deep at most, and every one stays inside its own lane.

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

const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();

const INCIDENTS: Task[] = [
  { id: "inc-1046", name: "INC-1046", color: "light-dark(#2563eb, #6b9bff)" },
  { id: "inc-1047", name: "INC-1047", color: "light-dark(#c2410c, #f08a52)" },
  { id: "inc-1048", name: "INC-1048", color: "light-dark(#7c3aed, #a98bfa)" },
];

const WORK: Subtask[] = [
  { id: "a", task: "inc-1046", lane: "priya", from: at(8), to: at(11) },
  { id: "b", task: "inc-1047", lane: "priya", from: at(9), to: at(12) },
  { id: "c", task: "inc-1048", lane: "priya", from: at(10), to: at(13) },
  { id: "d", task: "inc-1048", lane: "jonas", from: at(10), to: at(12) },
];

export default function ThreeAtOnce() {
  const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(7), at(14)] } });
  return (
    <Schedule ariaLabel="Priya on three incidents at once" height={150}>
      <Lane id="priya" label="Priya Raman" />
      <Lane id="jonas" label="Jonas Keller" />
      <Subtasks data={WORK} tasks={INCIDENTS} />
    </Schedule>
  );
}

When to use something else

Keyboard

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.
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.

Known limits

What umriss deliberately does not build, and why: ADR-0032.

Every page of @umriss-ui/schedule

Getting started

Plan

Time

Reading

Editing

Measuring

API index