# Subtasks

> Package `@umriss-ui/schedule`, version 0.40.0. Demo page: <https://romanhaendler.github.io/umriss-ui/schedule/subtasks/>. 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).

The bars: a main time with an optional lead-in before it and lead-out after it, in the colour of the task they belong to (also called bookings or assignments).

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

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

Lead-in and lead-out are durations beside the main time, not times of their own: moving the subtask takes them along. They are drawn faint in the task's colour, so preparation reads as belonging to the work and as not being it, and they occupy the lane as the main time does.

## Examples

### Main time, lead-in and lead-out

Loading before a round and unloading after it: `leadIn` and `leadOut` are durations beside the main time, drawn faint in the same colour.

```tsx
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-02", name: "T-02 Northfold", color: "light-dark(#2563eb, #6b9bff)" }];

const ROUNDS: Subtask[] = [
  { id: "plain", task: "t-02", lane: "van", from: at(6, 30), to: at(8) },
  { id: "loaded", task: "t-02", lane: "van", from: at(9), to: at(10, 30), leadIn: min(30) },
  { id: "loaded-and-unloaded", task: "t-02", lane: "van", from: at(11, 30), to: at(13), leadIn: min(20), leadOut: min(40) },
];

export default function LeadInAndLeadOut() {
  const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(6), at(14, 30)] } });
  return (
    <Schedule ariaLabel="Three rounds of one van" height={110}>
      <Lane id="van" label="Van FP 214 K" />
      <Subtasks data={ROUNDS} tasks={TOURS} />
    </Schedule>
  );
}
```

### Colour by what the task means

Every subtask of a task shares its `color`, and the meaning is yours: here the two projects due this week are in the danger colour.

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

/* A subtask whose task is missing from `tasks` is drawn muted: it is on the
   plan, and nobody said what it is. */

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

const PROJECTS: Task[] = [
  { id: "portal", name: "Member portal", color: "var(--u-color-danger)" },
  { id: "booking", name: "Booking app", color: "var(--u-color-danger)" },
  { id: "intranet", name: "Intranet", color: "var(--u-color-accent)" },
];

const WORK: Subtask[] = [
  { id: "w-105", task: "portal", lane: "chloe", from: at(16, 9), to: at(17, 17) },
  { id: "w-109", task: "portal", lane: "eva", from: at(18, 9), to: at(19, 17) },
  { id: "w-111", task: "booking", lane: "chloe", from: at(18, 9), to: at(19, 13) },
  { id: "w-113", task: "intranet", lane: "eva", from: at(16, 9), to: at(17, 13) },
  { id: "w-120", task: "not-listed", lane: "chloe", from: at(19, 14), to: at(20, 17) },
];

export default function TaskColours() {
  const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(16, 6), at(20, 20)] } });
  return (
    <Schedule ariaLabel="Projects due this week among the rest" height={150}>
      <Lane id="chloe" label="Chloe Durand" />
      <Lane id="eva" label="Eva Novak" />
      <Subtasks data={WORK} tasks={PROJECTS} />
    </Schedule>
  );
}
```

### Plan a sprint for a team

Work items straight from the team's tracker: the person is the lane, the project the task, and `name` is what the tooltip calls each one.

```tsx
import { useSchedule } from "@umriss-ui/schedule";
import type { Subtask } from "@umriss-ui/schedule";

/* Data from the planning world, written out here so the example runs on its own. */

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

interface Person {
  id: string;
  name: string;
  role: "Developer" | "Designer" | "Product manager" | "QA engineer";
  team: "Web" | "Apps";
  /** Hours a week they can be planned for. */
  capacity: number;
}

const PEOPLE: readonly Person[] = [
  { id: "maya", name: "Maya Lindgren", role: "Product manager", team: "Web", capacity: 32 },
  { id: "arjun", name: "Arjun Mehta", role: "Developer", team: "Web", capacity: 40 },
  { id: "chloe", name: "Chloe Durand", role: "Developer", team: "Web", capacity: 40 },
  { id: "noah", name: "Noah Fischer", role: "Designer", team: "Web", capacity: 24 },
  { id: "eva", name: "Eva Novak", role: "QA engineer", team: "Web", capacity: 40 },
  { id: "luis", name: "Luis Moreno", role: "Product manager", team: "Apps", capacity: 40 },
  { id: "hana", name: "Hana Sato", role: "Developer", team: "Apps", capacity: 40 },
  { id: "kofi", name: "Kofi Mensah", role: "Developer", team: "Apps", capacity: 32 },
  { id: "freya", name: "Freya Olsen", role: "Designer", team: "Apps", capacity: 40 },
  { id: "david", name: "David Kowalski", role: "QA engineer", team: "Apps", capacity: 20 },
];

interface Project {
  id: string;
  name: string;
  client: string;
  color: string;
}

const PROJECTS: readonly Project[] = [
  { id: "shop", name: "Online shop relaunch", client: "Pembury Home", color: "light-dark(#2563eb, #6b9bff)" },
  { id: "booking", name: "Booking app", client: "Saltmarsh Clinics", color: "light-dark(#0d9488, #3cc7b8)" },
  { id: "portal", name: "Member portal", client: "Rowan Credit Union", color: "light-dark(#7c3aed, #a98bfa)" },
  { id: "intranet", name: "Intranet", client: "Tidewell (internal)", color: "light-dark(#c2410c, #f08a52)" },
];

/** A work item, shaped as `@umriss-ui/schedule`'s `Subtask`. */
interface WorkItem {
  id: string;
  /** The project. */
  task: string;
  /** The person. */
  lane: string;
  from: number;
  to: number;
  name: string;
  sprint: string;
  /** Hours, as estimated at planning. */
  estimate: number;
  status: "to do" | "in progress" | "in review" | "done";
}

const item = (id: string, task: string, lane: string, from: number, to: number, name: string, estimate: number, status: WorkItem["status"]): WorkItem =>
  ({ id, task, lane, from, to, name, sprint: "sprint-14", estimate, status });

const WORK: readonly WorkItem[] = [
  item("w-101", "portal", "arjun", at(9), at(11, 17), "Sign-in with e-mail code", 18, "done"),
  item("w-102", "portal", "arjun", at(12), at(17, 17), "Profile page", 26, "in progress"),
  item("w-103", "portal", "chloe", at(9), at(10, 17), "Session handling", 12, "done"),
  item("w-104", "shop", "chloe", at(11), at(13, 17), "Basket keeps items across devices", 20, "in review"),
  item("w-105", "portal", "chloe", at(16), at(19, 17), "Change of address form", 24, "in progress"),
  item("w-106", "portal", "noah", at(9), at(12, 13), "Profile page design", 14, "done"),
  item("w-107", "shop", "noah", at(16), at(18, 17), "Search results layout", 12, "to do"),
  item("w-108", "portal", "eva", at(12), at(13, 17), "Test plan for sign-in", 10, "done"),
  item("w-109", "portal", "eva", at(17), at(20, 17), "Regression run", 20, "to do"),
  item("w-110", "booking", "hana", at(9), at(13, 17), "Reminder scheduling service", 32, "done"),
  item("w-111", "booking", "hana", at(16), at(20, 17), "Push notifications", 30, "in progress"),
  item("w-112", "booking", "kofi", at(10), at(12, 17), "Calendar sync", 18, "done"),
  item("w-113", "intranet", "kofi", at(16), at(18, 17), "News feed", 16, "in progress"),
  item("w-114", "booking", "freya", at(9), at(11, 17), "Reminder settings screen", 16, "done"),
  item("w-115", "booking", "freya", at(18), at(20, 17), "Store screenshots", 12, "to do"),
  item("w-116", "booking", "david", at(16), at(19, 13), "Device test matrix", 14, "to do"),
];

const SUBTASKS: Subtask[] = WORK.map((item): Subtask => ({
  ...item,
  /* Not started yet: drawn hollow, as planned but not begun. */
  ...(item.status === "to do" ? { appearance: ["provisional"] } : {}),
}));

const SPRINT_14: readonly [number, number] = [new Date(2026, 2, 9, 6).getTime(), new Date(2026, 2, 21).getTime()];

export default function ASprint() {
  const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: SPRINT_14 } });
  return (
    <Schedule ariaLabel="Sprint 14, 9 to 20 March" height={500} label={(s) => s.name ?? s.id}>
      {PEOPLE.map((person) => (
        <Lane key={person.id} id={person.id} label={person.name} />
      ))}
      <Subtasks data={SUBTASKS} tasks={PROJECTS} />
    </Schedule>
  );
}
```

## Keyboard

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

## API

### `SubtasksProps`

| Prop | Type | Default | Description |
|---|---|---|---|
| `data` *required* | `readonly `[`Subtask`](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#type-Subtask)`[]` | — | The subtasks, as the caller holds them. The schedule never changes one.<br>Shown in: [Main time, lead-in and lead-out](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#lead-in-and-lead-out), [Colour by what the task means](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#task-colours), [Plan a sprint for a team](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#a-sprint) and 78 more |
| `tasks` *required* | `readonly `[`Task`](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#type-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.<br>Shown in: [Main time, lead-in and lead-out](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#lead-in-and-lead-out), [Colour by what the task means](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#task-colours), [Plan a sprint for a team](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#a-sprint) and 78 more |

### `Subtask`

| Prop | Type | Default | Description |
|---|---|---|---|
| `id` *required* | `string` | — | The caller's identity of the subtask.<br>Shown in: [Main time, lead-in and lead-out](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#lead-in-and-lead-out), [Colour by what the task means](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#task-colours), [Plan a sprint for a team](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#a-sprint) and 75 more |
| `task` *required* | `string` | — | The id of the task it belongs to.<br>Shown in: [Main time, lead-in and lead-out](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#lead-in-and-lead-out), [Colour by what the task means](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#task-colours), [The smallest schedule (Installation)](https://romanhaendler.github.io/umriss-ui/schedule/installation/#smallest-schedule) and 71 more |
| `lane` *required* | `string` | — | The id of the lane it sits on.<br>Shown in: [Main time, lead-in and lead-out](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#lead-in-and-lead-out), [Colour by what the task means](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#task-colours), [The smallest schedule (Installation)](https://romanhaendler.github.io/umriss-ui/schedule/installation/#smallest-schedule) and 71 more |
| `from` *required* | `number` | — | Start of the main time.<br>Shown in: [Main time, lead-in and lead-out](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#lead-in-and-lead-out), [Colour by what the task means](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#task-colours), [The smallest schedule (Installation)](https://romanhaendler.github.io/umriss-ui/schedule/installation/#smallest-schedule) and 71 more |
| `to` *required* | `number` | — | End of the main time.<br>Shown in: [Main time, lead-in and lead-out](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#lead-in-and-lead-out), [Colour by what the task means](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#task-colours), [The smallest schedule (Installation)](https://romanhaendler.github.io/umriss-ui/schedule/installation/#smallest-schedule) and 71 more |
| `leadIn` | `number` | — | Duration of the lead-in before the main time; none is zero.<br>Shown in: [Main time, lead-in and lead-out](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#lead-in-and-lead-out), [The smallest schedule (Installation)](https://romanhaendler.github.io/umriss-ui/schedule/installation/#smallest-schedule), [A day of tours (First schedule)](https://romanhaendler.github.io/umriss-ui/schedule/schedule/#first-schedule) and 25 more |
| `leadOut` | `number` | — | Duration of the lead-out after the main time; none is zero.<br>Shown in: [Main time, lead-in and lead-out](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#lead-in-and-lead-out), [The smallest schedule (Installation)](https://romanhaendler.github.io/umriss-ui/schedule/installation/#smallest-schedule), [A day of tours (First schedule)](https://romanhaendler.github.io/umriss-ui/schedule/schedule/#first-schedule) and 22 more |
| `name` | `string` | — | What the subtask is called, where the schedule names it.<br>Shown in: [Plan a sprint for a team](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#a-sprint), [Write into the bars (Bar labels)](https://romanhaendler.github.io/umriss-ui/schedule/bar-labels/#bar-labels), [Join the stops of a tour (Dependencies)](https://romanhaendler.github.io/umriss-ui/schedule/dependencies/#stops-of-a-tour) and 28 more |
| `appearance` | `readonly `[`SubtaskAppearance`](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#type-SubtaskAppearance)`[]` | — | What the bar says besides its colour: `"provisional"`, `"fixed"`, `"muted"`, `"open"`. Several hold at once; `"provisional"` and `"fixed"` contradict, and the later one in the list wins.<br>Shown in: [Plan a sprint for a team](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#a-sprint), [Provisional (Appearances)](https://romanhaendler.github.io/umriss-ui/schedule/appearances/#provisional), [Fixed (Appearances)](https://romanhaendler.github.io/umriss-ui/schedule/appearances/#fixed) and 3 more |
| `progress` | `number` | — | The share of the work that is done, from 0 to 1, drawn as a filled part of the bar. Without it nothing is claimed - an empty bar and a bar at zero per cent are two different statements.<br>Shown in: [Progress (Appearances)](https://romanhaendler.github.io/umriss-ui/schedule/appearances/#progress), [Several at once (Appearances)](https://romanhaendler.github.io/umriss-ui/schedule/appearances/#combinations) |

### `Task`

| Prop | Type | Default | Description |
|---|---|---|---|
| `id` *required* | `string` | — | The caller's identity of the task.<br>Shown in: [Main time, lead-in and lead-out](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#lead-in-and-lead-out), [Colour by what the task means](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#task-colours), [The smallest schedule (Installation)](https://romanhaendler.github.io/umriss-ui/schedule/installation/#smallest-schedule) and 70 more |
| `color` *required* | `string` | — | Any CSS colour - a literal, a token, `light-dark(…)`. The colour means what the caller's domain needs it to mean.<br>Shown in: [Main time, lead-in and lead-out](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#lead-in-and-lead-out), [Colour by what the task means](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#task-colours), [The smallest schedule (Installation)](https://romanhaendler.github.io/umriss-ui/schedule/installation/#smallest-schedule) and 70 more |
| `name` | `string` | — | What the task is called, where the schedule names it.<br>Shown in: [Main time, lead-in and lead-out](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#lead-in-and-lead-out), [Colour by what the task means](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#task-colours), [The smallest schedule (Installation)](https://romanhaendler.github.io/umriss-ui/schedule/installation/#smallest-schedule) and 70 more |

### Types on this page

#### `SubtaskAppearance`

The appearances a caller may give a subtask.

```ts
type SubtaskAppearance = "provisional" | "fixed" | "muted" | "open";
```
