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.
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>
);
}
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>
);
}
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>
);
}
The keys of First schedule apply here.
SubtasksProps| Name | Type | Default | Description |
|---|---|---|---|
datarequired | readonly 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 |
tasksrequired | readonly 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| Name | Type | Default | Description |
|---|---|---|---|
idrequired | string | — | 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 |
taskrequired | string | — | 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 |
lanerequired | string | — | 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 |
fromrequired | number | — | 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 |
torequired | number | — | 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 |
leadIn | number | — | 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 |
leadOut | number | — | 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 |
name | string | — | 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 |
appearance | readonly 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 |
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. Shown in: Progress (Appearances), Several at once (Appearances) |
Task| Name | Type | Default | Description |
|---|---|---|---|
idrequired | string | — | 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 |
colorrequired | string | — | 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 |
name | string | — | 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 |
SubtaskAppearanceThe appearances a caller may give a subtask.
type SubtaskAppearance = "provisional" | "fixed" | "muted" | "open";