Task List
A live, agent-authored plan — an ordered list of steps marked pending, in progress, or completed as the agent works through them.
- In progress: Researching sourced “Seen in” credits for all 50 patterns
- Pending: Change seenIn to sourced entries and widen the product list
- Pending: Build, verify, commit, push and open PR
"use client";
import * as React from "react";
import { AnimatePresence, motion } from "motion/react";
import { Check } from "lucide-react";
import { cn } from "@/lib/utils";
export type TaskStatus = "pending" | "in_progress" | "completed";
export interface Task {
id: string;
/** Imperative label, e.g. "Run the build". */
label: string;
/** Optional present-continuous label shown while the task is in progress, e.g. "Running the build". */
activeLabel?: string;
status: TaskStatus;
}
export interface TaskListProps {
tasks: Task[];
/** Accessible name for the list. */
label?: string;
className?: string;
}
const STATUS_TEXT: Record<TaskStatus, string> = {
pending: "Pending",
in_progress: "In progress",
completed: "Completed",
};
export function TaskList({ tasks, label = "Task list", className }: TaskListProps) {
const done = tasks.filter((t) => t.status === "completed").length;
return (
<div className={cn("w-full rounded-2xl border bg-card px-4 py-3", className)}>
<ol aria-label={`${label}, ${done} of ${tasks.length} completed`} className="space-y-2.5">
{tasks.map((task) => (
<TaskRow key={task.id} task={task} />
))}
</ol>
</div>
);
}
function TaskRow({ task }: { task: Task }) {
const completed = task.status === "completed";
const active = task.status === "in_progress";
const text = active && task.activeLabel ? task.activeLabel : task.label;
return (
<li
aria-current={active ? "step" : undefined}
className="flex items-start gap-2.5"
>
<span className="relative mt-px flex size-4 shrink-0 items-center justify-center">
<AnimatePresence initial={false} mode="popLayout">
<motion.span
key={task.status}
initial={{ scale: 0.5, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.5, opacity: 0 }}
transition={{ type: "spring", stiffness: 420, damping: 26 }}
className="absolute inset-0 flex items-center justify-center"
>
<StatusIcon status={task.status} />
</motion.span>
</AnimatePresence>
<span className="sr-only">{STATUS_TEXT[task.status]}: </span>
</span>
<span
className={cn(
"min-w-0 flex-1 text-sm leading-snug transition-colors duration-300",
completed ? "text-muted-foreground" : "text-foreground",
active && "font-medium"
)}
>
{text}
</span>
</li>
);
}
function StatusIcon({ status }: { status: TaskStatus }) {
if (status === "completed") {
return (
<span className="flex size-4 items-center justify-center rounded-full bg-muted-foreground" aria-hidden>
<Check className="size-2.5 text-background" strokeWidth={3.5} />
</span>
);
}
if (status === "in_progress") {
return (
// A CSS spin, not a Motion `animate`: inside `AnimatePresence initial={false}`
// Motion skips the first animation of every nested motion element, so a
// task that is already in progress on mount would never start spinning.
<svg
viewBox="0 0 16 16"
className="size-4 text-foreground motion-safe:animate-spin motion-safe:[animation-duration:0.9s]"
fill="none"
aria-hidden
>
<circle cx="8" cy="8" r="6.25" stroke="currentColor" strokeOpacity={0.15} strokeWidth="1.5" />
<path d="M8 1.75a6.25 6.25 0 0 1 6.25 6.25" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
</svg>
);
}
return (
<svg viewBox="0 0 16 16" className="size-4 text-muted-foreground/60" fill="none" aria-hidden>
<circle
cx="8"
cy="8"
r="6.25"
stroke="currentColor"
strokeWidth="1.25"
strokeDasharray="2 2"
strokeLinecap="round"
/>
</svg>
);
}
A UX spec for this pattern — written for agents implementing or reusing it, not the code.
# Task List
## Summary
A compact, agent-authored plan: a short ordered list of steps the agent intends to take, each marked with a status glyph — a dashed ring while pending, a spinning arc while in progress, and a filled check once completed. It updates live as the agent works, so the user can see at a glance what's done, what's happening now, and what's next.
## When to use
- An agent is executing a multi-step task (coding, research, a migration) and has broken it into a plan up front.
- The user benefits from seeing the remaining scope — to judge how long the run will take, or to interrupt before a step they disagree with.
- Steps are coarse (3–8 items), each a meaningful unit of work rather than an individual tool call.
## When not to use
- A single undifferentiated operation — use Thinking Loader.
- Showing the raw sequence of tool calls or reasoning — use Expandable Trace; a task list is the plan, not the log.
- Steps the user must complete themselves — use Setup Checklist, which makes rows actionable.
- Parallel sub-agents each with their own steps — use Multi-Agent Trace.
## Anatomy
- Container: a bordered, rounded card holding an ordered list.
- Row: status glyph + label, top-aligned so wrapped labels stay readable.
- Status glyph — pending: a dashed, muted ring ("not started", without implying failure).
- Status glyph — in progress: a spinning partial arc over a faint track, in the foreground color.
- Status glyph — completed: a filled muted circle with a check.
- Label: one line where possible; the in-progress row is emphasised (medium weight) and completed rows fade to muted text.
## Behavior
- At most one task is in progress at a time in a sequential plan; the next pending task flips to in progress as the previous one completes.
- Status changes swap the glyph with a short spring (scale + fade) in place — rows never reorder or shift.
- The agent may revise the plan mid-run: new tasks are appended or inserted where they'll execute, and obsolete ones removed, without resetting completed rows.
- Optionally show a present-continuous label ("Running the build") while a task is in progress, reverting to the imperative label ("Run the build") once done.
- When every task is completed, the list stays visible as a record of what was done; no extra success state is needed.
## Content guidelines
- Write each label as an imperative verb phrase describing an outcome: "Add a Task List pattern", not "Adding stuff" or "Step 2".
- Keep labels concrete and scoped — the user should be able to tell what changes when a row turns complete.
- Prefer 3–8 tasks. Fewer means the plan isn't worth showing; more means the steps are too granular.
- Don't put timing, file paths, or tool names in the label — those belong in the trace.
## Accessibility
- Render as an ordered list (`<ol>`) with an accessible name that includes progress ("Agent plan, 1 of 3 completed").
- Each glyph is decorative (`aria-hidden`); expose status as visually hidden text before the label ("Completed: …", "In progress: …").
- Mark the in-progress row with `aria-current="step"`.
- Don't make the whole list a live region — announce only meaningful transitions (a task completing) if needed, to avoid chatter.
- Respect `prefers-reduced-motion`: stop the arc spinning; the partial arc alone still reads as "in progress".
- Status is never conveyed by color alone — each state has a distinct glyph shape.
## Related patterns
- **Setup Checklist** — Use it instead when the rows are steps the person completes rather than steps the agent works through.
- **Multi-Agent Trace** — Use it instead when the plan is split across parallel sub-agents.
- **Expandable Trace** — Use it as well when people need the detailed log of what happened during each step.
- **Background Tasks Drawer** — Use it instead when the work runs long and outside the conversation.
Do’s and don’ts
Do use it for
- An agent is executing a multi-step task (coding, research, a migration) and has broken it into a plan up front.
- The user benefits from seeing the remaining scope — to judge how long the run will take, or to interrupt before a step they disagree with.
- Steps are coarse (3–8 items), each a meaningful unit of work rather than an individual tool call.
Don’t use it for
- A single undifferentiated operation — use Thinking Loader.
- Showing the raw sequence of tool calls or reasoning — use Expandable Trace; a task list is the plan, not the log.
- Steps the user must complete themselves — use Setup Checklist, which makes rows actionable.
- Parallel sub-agents each with their own steps — use Multi-Agent Trace.
Related patterns
Setup Checklist
Use it instead when the rows are steps the person completes rather than steps the agent works through.
Multi-Agent Trace
Use it instead when the plan is split across parallel sub-agents.
Expandable Trace
Use it as well when people need the detailed log of what happened during each step.
Background Tasks Drawer
Use it instead when the work runs long and outside the conversation.