Visitar URL original
Agentic UX Patterns: 4 Frontend Primitives for AI Agents
← All posts

Agentic UX Primitives: The Frontend Patterns Nobody Taught You

The chatbot is dead

If you’re building an AI product in 2026 and the 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 winning:

  • Cursor shows diffs across several files with inline approval controls instead of a chat thread.
  • Claude Code exposes its reasoning, shows tool calls as they happen, and lets you interrupt it mid-execution.
  • Perplexity reveals sources as it finds them, so you see the evidence before the answer.

None of these products invented a new model. They built new UX patterns, and most frontend engineers haven’t learned them yet because no component library ships them. There’s no npm install agentic-ux. You have to build them yourself.

Here are four that every AI product needs.

Pattern 1: Streaming with intent

The naive approach is to open a connection and append tokens to a div as they arrive.

That works, but at 100+ tokens per second the text jitters and gets hard to read.

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);
  }
}

Without batching, the DOM updates on every token. That’s hundreds of layout recalculations per second and a lot of dropped frames. With batching, updates line up with the browser’s paint cycle and the text flows smoothly.

Vercel AI SDK 6 does this internally in useChat. If you’re building your own streaming UI (and you probably should, because the default chat UX is limiting), you need to understand this pattern.

You can go further and move Markdown parsing off the main thread. A WebAssembly-compiled renderer running in a Web Worker parses streamed content without blocking the UI. Add content-visibility: auto to off-screen messages and you can handle arbitrarily long conversations without jank.

Pattern 2: Human-in-the-loop gates

Most AI UIs get this question wrong: when should the AI just do the thing, and when should it ask first?

It depends on the stakes. Low-stakes actions like reformatting code or generating a summary should happen automatically. High-stakes actions like writing to a database, deploying to production or sending an email need a gate, a point where the user reviews and approves before anything runs.

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 };
      },
    },
  },
});

The flag only triggers the gate. Most of the work is in what the gate shows the user:

  • A bad gate is a modal that says “The AI wants to deploy v2.3.1. Allow?”
  • A good gate is a card with what changed since the last deploy, which tests passed, the rollback plan and a diff of configuration changes, with clear “Deploy” and “Cancel” buttons.

Cursor gets this right with diff-first review. Instead of asking “apply these changes?”, it shows you exactly what will change in every file, and you accept or reject individual hunks. You review a concrete artifact before anything happens.

A good approval gate shows me what the world will look like after the agent acts.

Pattern 3: Reasoning visualization

Users trust AI output more when they can watch the AI work: what it’s reading, which tools it calls, what it considered and rejected. This holds 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>
  );
}

Most of the trust comes from the trace existing at all. What’s inside it matters less.

Perplexity does this well. Each source appears in the sidebar as soon as it’s found, before the answer is done, so by the time the answer shows up the user has already seen the evidence.

Claude’s thinking accordions work the same way. The reasoning is collapsed by default because most users don’t read it, but it’s there on demand, and knowing it’s there makes people more confident in the output.

The anti-pattern is showing nothing. A spinner followed by a finished answer feels like a magic trick: impressive, but you wouldn’t trust a magic trick with an important decision.

Pattern 4: Confidence indicators

Should an AI interface show how confident the model is? People disagree on this one.

LLM confidence scores are mostly meaningless. They measure token probability, which says nothing about factual accuracy, and a model can be very “confident” about a hallucination.

Visual confidence indicators can still help in the UI if you build them 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);
}

Derive confidence from structural signals instead of asking the model to rate itself:

  • High: the response uses verified sources (RAG with known-good documents), the tool call succeeded, the code compiles and tests pass.
  • Medium: the response mixes retrieved data with model knowledge, or the tool call returned partial results.
  • Low: the response is purely generated with no grounding data, or the model flagged uncertainty in its reasoning trace.

Built on signals like these, the indicators help users decide how much to trust a response. A color-coded border around vibes is theater, and it’s worse than showing nothing. Use them only when you have real structural signals.

The same patterns, one layer down

Each of these primitives depends on something in the backend. When the backend doesn’t provide it, the UI has nothing honest to show.

  • Streaming with intent needs tools that report progress as they go. If a tool sits silent for 40 seconds and then returns one giant JSON payload, skeleton loaders won’t save the experience.
  • HITL gates need tools that keep reads and writes apart. You can only gate a dangerous call if it’s a separate, recognizable operation. I made that case at length in MCP Tool Design for AI Agents, Not API Endpoints.
  • Reasoning visualization needs errors that explain themselves. Nobody can read a trace full of "Invalid payload". An error that tells the agent how to fix its next call also tells the user what just happened.
  • Confidence indicators need grounded context. An agent guessing at your conventions will sound sure and still be wrong. One working from structured project context has an actual reason to be confident.

I use the same stakes rule in my own tooling. My coding agent is read-only by default and asks before it writes, which is Pattern 2 running in a terminal.

So when one of these patterns feels impossible to build, look at the tools underneath it first.

The trust stack

The four patterns build on each other:

  1. Streaming with intent shows the user the AI is working, which cuts perceived latency.
  2. Reasoning visualization shows how it’s working.
  3. HITL gates give the user control over what it does.
  4. Confidence indicators tell the user how far to trust the output.

Together they address the main UX problem of AI products: users don’t trust them by default, and they’re right not to. These patterns give users something concrete to base that trust on.

The tooling is catching up. assistant-ui and CopilotKit package some of these as composable React primitives, and the AG-UI protocol standardizes 16 event types for bidirectional agent-UI communication.

The design decisions are still yours: when to gate, what to visualize, how to signal confidence.

Ship an AI product without these patterns and users won’t trust it. They might still use it, but they’ll copy the output into another window and check it by hand, which turns your product into a very expensive autocomplete.