59 / 122 · Agents
Agent plan
A checklist the agent works through, with progress you can glance.
Plan0 of 5
- Read existing composer state
- Design the draft store
- Wire runtime persistence
- Add regression tests
- Update the docs
Installation
npx shadcn@latest add "@assistant-ui/elements-agent-plan"
Usage
import { AgentPlan } from "@/components/elements/agent-plan";
<AgentPlan
steps={["Read the file", "Draft the fix", "Run tests"]}
activeIndex={1}
/>Props
steps*readonly string[]Checklist items the agent works through in order.
activeIndex*numberIndex of the step currently running. Values past the end mark every step done, and values before the start mark none.
classNamestringExtra classes merged onto the root.
This element normalizes its counts and shares at the boundary, so a value from outside its range reads as the nearest end rather than reaching the DOM: a negative count shows nothing, one past the end shows everything, and a share stays within 0 to 100 percent. A prop that names a selection is the exception, and means nothing is selected.
Source
agent-plan.tsx"use client";
import type { ComponentProps } from "react";
import { CheckIcon, Loader2Icon } from "lucide-react";
import { cn } from "@/lib/utils";
import { mono } from "./surfaces";
import { pct, progressOf } from "./range";
export function AgentPlan({
steps,
activeIndex,
className,
...props
}: Omit<ComponentProps<"div">, "children" | "steps" | "activeIndex"> & {
steps: readonly string[];
activeIndex: number;
}) {
const total = steps.length;
const completed = progressOf(activeIndex, total);
const allDone = completed >= total;
const progress = pct(completed, total);
return (
<div
data-slot="agent-plan"
className={cn("flex w-full max-w-sm flex-col gap-3", className)}
{...props}
>
<div className="flex items-center justify-between">
<span className="text-[13.5px] font-medium">Plan</span>
<span className={cn(mono, "text-foreground/35 tabular-nums")}>
{completed} of {total}
</span>
</div>
<div className="bg-foreground/[0.06] h-[3px] w-full overflow-hidden rounded-full">
<span
className="bg-foreground/80 block h-full rounded-full transition-[width] duration-500"
style={{ width: `${progress}%` }}
/>
</div>
<ul className="flex flex-col gap-2.5">
{steps.map((step, i) => {
const done = allDone || i < completed;
const active = !allDone && i === completed;
return (
<li key={step} className="flex items-center gap-2.5 text-[13.5px]">
<span className="flex size-4 shrink-0 items-center justify-center">
{done ? (
<CheckIcon className="text-foreground/35 size-3.5" />
) : active ? (
<Loader2Icon className="text-foreground/90 size-3.5 animate-spin motion-reduce:animate-none" />
) : (
<span
aria-hidden
className="bg-foreground/15 size-1.5 rounded-full"
/>
)}
</span>
<span
className={cn(
done && "text-foreground/40",
active && "text-foreground/90",
!done && !active && "text-foreground/35",
)}
>
{step}
</span>
</li>
);
})}
</ul>
</div>
);
}