affectUI › Components › Agents
Decision Tree
An agent stuck on a decision lays out its options as a tree – the consequences of each branch, the risky ones marked, its own pick highlighted – and you choose a path or write your own.
Part of affectUI Pro. Install: npx shadcn@latest add https://www.affectui.com/r/decision-tree.json · Import from @/components/ui/decision-tree
When to use
- An agent hits a fork it shouldn't take alone – it asks how to proceed and shows what each way would change
- Backend calls without much to look at: data cleanups, refunds, rollouts, escalations – the consequences are the content
- When the agent has a preference but a person decides – its pick is marked, the other ways stay one click away
When not to use
- Reviewing finished work – a diff, an email, a refund – one item after another: use Review Center
- One action that only needs a yes or no, with a countdown – use Approval Card
- A few quick questions with two answers each – use Decision Inbox
- An agent asking for access to a tool or scope – use Permission Request
Usage
import { DecisionTree, type Decision } from "@/components/ui/decision-tree";
const decisions: Decision[] = [
{
id: "dupes",
question: "3 customers have two active subscriptions – how should I proceed?",
context: "All three signed up twice during the checkout outage.",
agent: "Billing agent", model: "Sonnet 5.5", risk: "high",
options: [
{ label: "Cancel the newer ones", pick: true, effects: ["Cancels 3 subscriptions · reversible", "Sends 3 emails"] },
// A flagged part: marked in the leaf, its note shows on hover or with F
{ label: "Refund the newer ones", effects: [["Refunds ", { text: "$147 · not reversible", note: "One charge is two cycles old." }]] },
{ label: "Ask the customers", effects: ["Sends 3 emails · waits for answers"] },
],
},
];
export function Decisions() {
return (
<DecisionTree
decisions={decisions}
onChoose={async (decision, choice) => {
if ("other" in choice) return await agent.instruct(decision.id, choice.other); // its answer shows under your branch
await agent.resume(decision.id, choice.option.label);
}}
/>
);
}Props
| Name | Type | Default | Description |
|---|---|---|---|
decisions | Decision[] | { id, question, context?, agent, model?, icon?, risk?, options } – everything waiting for a call; decided ones leave, new ids join at the end | |
options | DecisionOption[] | Per decision, two to four – { label, effects?, pick? }; effects are what the branch leads to, one line each; pick marks the agent's own preference | |
effects | DecisionText[] | Per option – a string, or an array of strings and { text, note, source? } flags; flags are marked and explain themselves on hover, focus or F | |
risk | "high" | "medium" | "low" | Per decision – high and medium show as a small tag next to the agent | |
onChoose | (decision, choice) => string | void | Promise<string | void> | A branch was picked ({ index, option }) or an instruction written ({ other }) – for an instruction, return the agent's short answer | |
allowOther | boolean | true | Shows the "Other…" branch for your own instruction |
ref | Ref<DecisionTreeHandle> | hover, choose, setOther, send, next, previous, peek | |
title | string | "Decisions" | Heading of the bar |
className | string | Extra class on the root |
Keyboard
- 1 / 2 / 3 / 4: Picks a branch; the number after the last option opens "Other…"
- Enter: In "Other…": sends your instruction – Shift+Enter starts a new line
- ArrowRight / ArrowLeft: Next and previous decision (J and K too)
- F: Shows the agent's note on the next flagged consequence
- Esc: Closes "Other…" or a note
Accessibility
- Every branch is a real button labelled with its option and, where it applies, "the agent's pick"; shortcuts are set with aria-keyshortcuts
- Flagged consequences are focusable and point to their note with aria-describedby (role tooltip)
- The "Other…" field is a labelled textarea; it has no focus ring of its own – its outline darkens instead, so focus stays visible
- Picks, instructions and the agent's answers are announced in a polite live region
- The lines are decoration and hidden from assistive tech
Responsive
- Built on a container query: wide, the tree runs left to right – question, branches, consequences
- Under 640px it runs top to bottom – the branches hang off a rail under the question, each with its consequences below it; nothing scrolls sideways
- Dark mode built in: switches when an ancestor (usually <html>) has the class dark or data-theme="dark"
Requirements
- React 19
Related
- Review Center – A focused workspace for what your agents want to do – the queue sorted by risk, one item at a time with the shaky spans marked, Approve, Edit, Reject and Skip on single keys, a stamp as each one leaves and the next one rising in
- Approval Card – An agent asks before it acts – risk badge, preview, Approve and Deny with shortcuts, a pausing countdown, and a smooth morph into a resolved row
- Decision Inbox – Open decisions with two quick answers each – answered ones confirm and fold away
- Agent Tree – Who started whom – an orchestrator and its sub-agents as a live tree, with status, cost and time on every card, costs rolling up each branch, and branches that fold to one line