One row per person, vehicle or room, named by its header and kept in the order it is declared, however many there are (also called resources).
import { useSchedule } from "@umriss-ui/schedule";
A lane is declared by its id, the one subtasks name, and a label. The label is content, not a string: a name with a plate number, a status beside it. It is real text, read by a screen reader, and it stays at the left edge while the plot pans.
A lane keeps its height whatever lies on it. Nothing is moved to another lane to make room: two subtasks on one lane at once are a finding (see Overlap).
A label is content, not a string: here the vehicle with its plate, and a badge for the van that is off the road.
import { Badge, Stack, Text } from "@umriss-ui/core";
import { useSchedule } from "@umriss-ui/schedule";
import type { Subtask, Task } from "@umriss-ui/schedule";
const at = (hours: number) => new Date(2026, 2, 17, hours).getTime();
const TOURS: Task[] = [{ id: "t-02", name: "T-02 Northfold", color: "light-dark(#0d9488, #3cc7b8)" }];
const LEGS: Subtask[] = [
{ id: "t-02-1", task: "t-02", lane: "v1", from: at(7), to: at(9) },
{ id: "t-02-2", task: "t-02", lane: "v2", from: at(9), to: at(11) },
];
export default function LaneHeaders() {
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(6), at(12)] } });
return (
<Schedule ariaLabel="Two vans of the North depot" height={150} headerWidth={210}>
<Lane
id="v1"
label={
<Stack direction="row" gap={1} align="center">
<Text size="sm">Van</Text>
<Text size="xs" tone="muted" mono>
FP 214 K
</Text>
</Stack>
}
/>
<Lane
id="v2"
label={
<Stack direction="row" gap={1} align="center" wrap>
<Text size="sm">E-van</Text>
<Text size="xs" tone="muted" mono>
FP 377 K
</Text>
<Badge tone="warning">Workshop</Badge>
</Stack>
}
/>
<Subtasks data={LEGS} tasks={TOURS} />
</Schedule>
);
}
Twenty vans in 300 pixels: the lanes scroll with their headers, while the time bands and the header column hold still.
import { useSchedule } from "@umriss-ui/schedule";
import type { Subtask, Task } from "@umriss-ui/schedule";
/* `height` is the whole schedule, both bands included; `laneHeight` and
`headerWidth` set the rest. */
const DEPOTS: Task[] = [
{ id: "north", name: "North depot", color: "light-dark(#2563eb, #6b9bff)" },
{ id: "river", name: "Riverside depot", color: "light-dark(#0d9488, #3cc7b8)" },
];
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
const VANS = Array.from({ length: 20 }, (_, i) => ({ id: `van-${i + 1}`, label: `Van ${String(i + 1).padStart(2, "0")}` }));
/* One round per van, each starting a quarter hour after the one above. */
const ROUNDS: Subtask[] = VANS.map((van, i) => ({
id: `${van.id}-round`,
task: i % 2 === 0 ? "north" : "river",
lane: van.id,
from: at(7, 15 * i),
to: at(9, 15 * i),
leadIn: 15 * 60_000,
}));
export default function ManyLanes() {
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(6), at(14)] } });
return (
<Schedule
ariaLabel="Twenty vans, Tuesday morning"
height={300}
laneHeight={32}
headerWidth={110}
>
{VANS.map((van) => (
<Lane key={van.id} id={van.id} label={van.label} />
))}
<Subtasks data={ROUNDS} tasks={DEPOTS} />
</Schedule>
);
}
The keys of First schedule apply here.
LaneProps| Name | Type | Default | Description |
|---|---|---|---|
idrequired | string | — | The identity subtasks name in their lane.Shown in: What a lane header says, Scroll more lanes than fit, The smallest schedule (Installation) and 78 more |
label | ReactNode | id | What the lane header says - the person, vehicle or room. Real text, read by a
screen reader. Shown in: What a lane header says, Scroll more lanes than fit, The smallest schedule (Installation) and 77 more |