Agentic UX Primitives: The Frontend Patterns Nobody Taught You
The chatbot is dead
If you’re building an AI product in 2026 and your UI is still a text input wired to a streaming response, you’re building the jQuery of AI interfaces.
Look at the products that are actually winning:
- Cursor doesn’t show you a chat thread — it shows you diffs across multiple files with inline approval controls
- Claude Code doesn’t just stream text — it exposes its reasoning, shows tool use in real time, and lets you interrupt mid-execution
- Perplexity doesn’t dump an answer — it progressively reveals sources as they’re discovered, building trust with every step
These products didn’t invent new models. They invented new UX patterns. And most frontend engineers haven’t learned them yet because they don’t exist in any component library. There’s no npm install agentic-ux. You have to build them.
Here are the four primitives every AI product needs.
Pattern 1: Streaming with intent
The naive approach to streaming is simple: open a connection, append tokens to a div as they arrive.
It works. But it creates a jittery, unreadable experience — especially at 100+ tokens per second.
The fix is adaptive token batching: buffer incoming tokens and flush them to the DOM only during idle frames. The browser’s scheduler.yield() API makes this clean:
async function streamWithIntent(
reader: ReadableStreamDefaultReader<string>,
onChunk: (text: string) => void,
) {
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += value;
// Yield to the browser — flush buffer during idle time
await scheduler.yield();
if (buffer.length > 0) {
onChunk(buffer);
buffer = '';
}
}
// Flush any remaining content
if (buffer.length > 0) {
onChunk(buffer);
}
}
The difference is dramatic. Without batching, the DOM updates on every single token — hundreds of layout recalculations per second, dropped frames, a UI that feels like it’s having a seizure. With batching, the text flows smoothly because updates are aligned to the browser’s paint cycle.
Vercel AI SDK 6 handles this internally in useChat. But if you’re building your own streaming UI — and you probably should be, because the default chat UX is limiting — understanding this pattern is essential.
Going further: move Markdown parsing off the main thread entirely. A WebAssembly-compiled renderer running in a Web Worker parses streamed content without blocking the UI thread. Combine that with content-visibility: auto on off-screen messages, and you can handle arbitrarily long conversations without jank.
Pattern 2: Human-in-the-loop gates
Here’s a question most AI UIs get wrong: when should the AI just do the thing, and when should it ask for permission?
The answer is stakes.
Low-stakes actions — reformatting code, generating a summary — should happen automatically. High-stakes actions — writing to a database, deploying to production, sending an email — need a gate. A moment where the user reviews and approves before the action executes.
Vercel AI SDK 6 makes the implementation a single flag:
const agent = new Agent({
model: openai('gpt-4o'),
tools: {
deployToProduction: {
description: 'Deploy the current build to production',
parameters: z.object({ version: z.string() }),
needsApproval: true, // <- this is the whole pattern
execute: async ({ version }) => {
await deploy(version);
return { status: 'deployed', version };
},
},
},
});
But the flag is just the trigger. The UX of the gate itself is where the real work happens:
- Bad gate UX: A modal that says “The AI wants to deploy v2.3.1. Allow?”
- Good gate UX: A card showing what changed since the last deploy, which tests passed, the rollback plan, and a diff of configuration changes — with “Deploy” and “Cancel” as clear actions
Cursor gets this right with its diff-first review pattern. Instead of asking “apply these changes?”, it shows you exactly what will change across every file. You accept or reject individual hunks. You’re reviewing a concrete artifact, not approving an abstract action.
The principle: approval gates should show the consequences, not describe the action.
Don’t tell me what you’re about to do. Show me what the world will look like after you do it.
Pattern 3: Reasoning visualization
Trust in AI is built through transparency.
When a user can see the AI working — what it’s reading, what tools it’s calling, what it considered and rejected — they trust the output more. Even when the output is identical.
The simplest version is a collapsible reasoning trace:
interface ReasoningStep {
type: 'thinking' | 'tool_call' | 'tool_result' | 'decision';
label: string;
content: string;
timestamp: number;
status: 'in_progress' | 'completed' | 'failed';
}
function ReasoningTrace({ steps }: { steps: ReasoningStep[] }) {
return (
<div className="reasoning-trace">
{steps.map((step, i) => (
<details key={i} open={step.status === 'in_progress'}>
<summary className={`step-${step.type} step-${step.status}`}>
<StatusIcon status={step.status} />
<span>{step.label}</span>
<time>{formatElapsed(step.timestamp)}</time>
</summary>
<div className="step-content">
{step.type === 'tool_call'
? <CodeBlock>{step.content}</CodeBlock>
: <Markdown>{step.content}</Markdown>
}
</div>
</details>
))}
</div>
);
}
The key insight is that the visualization itself builds trust — not the content inside it.
Perplexity demonstrates this beautifully. As it searches, each source appears in the sidebar the moment it’s found — not after the answer is complete. The user watches the research happen in real time. By the time the answer appears, they’ve already seen the evidence.
Claude’s thinking accordions work the same way. The reasoning is collapsed by default (most users don’t read it), but it’s available on demand. The mere presence of “here’s my reasoning” makes users more confident in the output.
The anti-pattern is showing nothing. A loading spinner followed by a complete answer feels like a magic trick — impressive, but you don’t trust magic tricks with important decisions.
Pattern 4: Confidence indicators
This one is controversial: should AI interfaces show how confident the model is?
The honest answer: LLM confidence scores are mostly meaningless. They measure token probability, not factual accuracy. A model can be highly “confident” about a hallucination.
But visual confidence indicators still serve a UX purpose — when done right:
.ai-response[data-confidence='high'] {
border-left: 3px solid var(--color-success);
}
.ai-response[data-confidence='medium'] {
border-left: 3px solid var(--color-warning);
}
.ai-response[data-confidence='low'] {
border-left: 3px solid var(--color-error);
background: var(--color-bg-caution);
}
The key: confidence should be derived from structural signals, not model self-assessment:
- High confidence — the response uses verified sources (RAG with known-good documents), the tool call succeeded, the code compiles and tests pass
- Medium confidence — the response mixes retrieved data with model knowledge, or the tool call returned partial results
- Low confidence — the response is purely generated with no grounding data, or the model flagged uncertainty in its reasoning trace
When confidence indicators are honest signals derived from the system’s actual certainty, they genuinely help users calibrate trust. When they’re just a color-coded wrapper around vibes, they’re worse than useless. They’re theater.
Use them when you have real structural signals. Skip them when you don’t.
The trust stack
These four patterns aren’t independent. They’re a stack:
- Streaming with intent shows the user that the AI is working → reduces perceived latency
- Reasoning visualization shows how the AI is working → builds transparency
- HITL gates give the user control over what the AI does → establishes boundaries
- Confidence indicators tell the user how much to trust the output → calibrates expectations
Together, they solve the fundamental UX problem of AI products: the trust gap.
Users don’t trust AI by default — and they shouldn’t. Trust is built through transparency, control, and honest signals. These patterns are how you build it.
The infrastructure is catching up. Libraries like assistant-ui and CopilotKit are packaging some of these as composable React primitives. The AG-UI protocol standardizes 16 event types for bidirectional agent-UI communication.
But the design decisions — when to gate, what to visualize, how to signal confidence — those are still yours. No library can make those choices for you. That’s why these are patterns, not components.
If you’re building AI products without them, your users don’t trust your AI. They might still use it — but they’re copying the output into a separate window and checking it manually.
That’s not augmentation. That’s a very expensive autocomplete.
Trust is the new conversion rate. Build for it.