Plan
The agent's proposed plan, from the first streamed step to the last one done: approve, edit or reject it before anything runs.
On this page
A coding agent proposes a plan, waits for approval, then works through its todos with subagents folded to one line.
Installation
Usage
import { Plan } from "@/components/ui/chatcn/plan"
<Plan
title="Make /runs paint before the data arrives"
steps={steps}
state="awaiting-approval"
onApprove={(steps) => approve(steps)}
onReject={(reason) => reject(reason)}
onStepsChange={setSteps}
/>state drives everything. While running, steps without an explicit status are derived from currentStepId: earlier steps are done, the current one is highlighted, later ones wait. Pass collapsible once the plan scrolls up the transcript and its header keeps showing Step 3 of 5.
Examples
Every state
Drafting, awaiting approval, running, done, rejected, and steps that failed, blocked or were skipped.
Approval
Nothing runs until the user approves. Reject opens a field for a note, because telling the agent what to change is more useful than a bare no.
Approve plan, edit it first, or reject it with a note for the agent.
Editing
Edit plan turns every step into a line you type in. Enter adds a step below, Backspace in an empty step removes it, and the grip handles reorder by drag or by arrow keys.
Type in any step, press Enter for a new one, drag or arrow-key the handles to reorder.
Streaming
Steps arrive one at a time while the model writes them.
States
| State | Shows |
|---|---|
drafting | Steps rise in as they stream, the square caret on the last one, Drafting shimmer |
awaiting-approval | Numbered steps, Reject, Edit plan and Approve plan |
approved | A quiet confirmation line while the agent starts |
running | Status glyphs, the current step highlighted, Step 2 of 5 |
done | Every check drawn, 5 of 5 done |
rejected | The note the user left, nothing ran |
Props
| Prop | Type | Notes |
|---|---|---|
title | ReactNode | What the plan achieves |
description | ReactNode | A sentence or two above the steps |
steps | PlanStep[] | { id, title, detail?, status? } |
state | PlanState | One of the six states above |
currentStepId | string | The step in progress while running |
onApprove | (steps) => void | Receives the steps as the user left them |
onReject | (reason?) => void | The note typed for the agent |
onStepsChange | (steps) => void | Enables Edit plan; called on Save plan |
rejectedReason | ReactNode | Shown in the rejected state |
editing, defaultEditing, onEditingChange | Edit mode, controlled or not | |
collapsible, open, defaultOpen, onOpenChange | Fold the steps under the header |
Keyboard
| Key | Action |
|---|---|
Enter in a step | Add a step below and move into it |
Backspace in an empty step | Remove it and move to the one above |
Alt + ArrowUp, ArrowDown in a step | Move the step |
ArrowUp, ArrowDown, Home, End on a handle | Move the step |
Cmd or Ctrl + Enter | Save plan |
Esc | Cancel editing, or leave the reject note |
Accessibility
- The plan is a labelled region; steps are an ordered list and the current one has
aria-current="step". - The state label is a polite live region: screen readers hear Awaiting approval and Step 3 of 5, not every token.
- Every handle names its step and announces each move: Moved step 2 to position 4 of 5.
- Step text inputs ignore Enter while an IME is composing, so Japanese and Chinese input never adds stray steps.
Motion
- Streamed steps rise 4 px and fade in over 200 ms. Steps present at mount appear at once.
- While dragging, the row follows the pointer 1:1 and the others slide aside over 200 ms; on drop it settles into its slot in 160 ms. Keyboard moves are instant.
- The highlight moves between steps with a 150 ms background fade. Under reduced motion, only fades remain.