# Lanes

> Package `@umriss-ui/schedule`, version 0.40.0. Demo page: <https://romanhaendler.github.io/umriss-ui/schedule/lane/>. Every page in one line: [llms.txt](https://romanhaendler.github.io/umriss-ui/schedule/llms.txt); every page in full: [llms-full.txt](https://romanhaendler.github.io/umriss-ui/schedule/llms-full.txt).

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

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

Demo page: https://romanhaendler.github.io/umriss-ui/schedule/lane/

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](https://romanhaendler.github.io/umriss-ui/schedule/overlap/)).

## Examples

### What a lane header says

A `label` is content, not a string: here the vehicle with its plate, and a badge for the van that is off the road.

```tsx
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>
  );
}
```

### Scroll more lanes than fit

Twenty vans in 300 pixels: the lanes scroll with their headers, while the time bands and the header column hold still.

```tsx
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>
  );
}
```

## When to use something else

- Lanes that belong together, folded into one row → Lane groups

## Keyboard

The keys of [First schedule](https://romanhaendler.github.io/umriss-ui/schedule/schedule/#keyboard-schedule) apply here.

## API

### `LaneProps`

| Prop | Type | Default | Description |
|---|---|---|---|
| `id` *required* | `string` | — | The identity subtasks name in their `lane`.<br>Shown in: [What a lane header says](https://romanhaendler.github.io/umriss-ui/schedule/lane/#lane-headers), [Scroll more lanes than fit](https://romanhaendler.github.io/umriss-ui/schedule/lane/#many-lanes), [The smallest schedule (Installation)](https://romanhaendler.github.io/umriss-ui/schedule/installation/#smallest-schedule) and 78 more |
| `label` | `ReactNode` | `id` | What the lane header says - the person, vehicle or room. Real text, read by a screen reader.<br>Shown in: [What a lane header says](https://romanhaendler.github.io/umriss-ui/schedule/lane/#lane-headers), [Scroll more lanes than fit](https://romanhaendler.github.io/umriss-ui/schedule/lane/#many-lanes), [The smallest schedule (Installation)](https://romanhaendler.github.io/umriss-ui/schedule/installation/#smallest-schedule) and 77 more |
