Subtasks

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

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

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.

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.

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.

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

API

SubtasksProps

NameTypeDefaultDescription
datarequiredreadonly Subtask[]—The subtasks, as the caller holds them. The schedule never changes one.

Shown in: Main time, lead-in and lead-out, Colour by what the task means, Plan a sprint for a team and 78 more

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.

Shown in: Main time, lead-in and lead-out, Colour by what the task means, Plan a sprint for a team and 78 more

Subtask

NameTypeDefaultDescription
idrequiredstring—The caller's identity of the subtask.

Shown in: Main time, lead-in and lead-out, Colour by what the task means, Plan a sprint for a team and 75 more

taskrequiredstring—The id of the task it belongs to.

Shown in: Main time, lead-in and lead-out, Colour by what the task means, The smallest schedule (Installation) and 71 more

lanerequiredstring—The id of the lane it sits on.

Shown in: Main time, lead-in and lead-out, Colour by what the task means, The smallest schedule (Installation) and 71 more

fromrequirednumber—Start of the main time.

Shown in: Main time, lead-in and lead-out, Colour by what the task means, The smallest schedule (Installation) and 71 more

torequirednumber—End of the main time.

Shown in: Main time, lead-in and lead-out, Colour by what the task means, The smallest schedule (Installation) and 71 more

leadInnumber—Duration of the lead-in before the main time; none is zero.

Shown in: Main time, lead-in and lead-out, The smallest schedule (Installation), A day of tours (First schedule) and 25 more

leadOutnumber—Duration of the lead-out after the main time; none is zero.

Shown in: Main time, lead-in and lead-out, The smallest schedule (Installation), A day of tours (First schedule) and 22 more

namestring—What the subtask is called, where the schedule names it.

Shown in: Plan a sprint for a team, Write into the bars (Bar labels), Join the stops of a tour (Dependencies) and 28 more

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

Shown in: Plan a sprint for a team, Provisional (Appearances), Fixed (Appearances) and 3 more

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

Shown in: Progress (Appearances), Several at once (Appearances)

Task

NameTypeDefaultDescription
idrequiredstring—The caller's identity of the task.

Shown in: Main time, lead-in and lead-out, Colour by what the task means, The smallest schedule (Installation) and 70 more

colorrequiredstring—Any CSS colour - a literal, a token, light-dark(…). The colour means what the caller's domain needs it to mean.

Shown in: Main time, lead-in and lead-out, Colour by what the task means, The smallest schedule (Installation) and 70 more

namestring—What the task is called, where the schedule names it.

Shown in: Main time, lead-in and lead-out, Colour by what the task means, The smallest schedule (Installation) and 70 more

Types on this page

SubtaskAppearance

The appearances a caller may give a subtask.

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

Every page of @umriss-ui/schedule

Getting started

Plan

Time

Reading

Editing

Measuring

API index