← Back to homeui-ux

Designing for the AI Era: UX Patterns for Streaming, Uncertainty, and Trust

← All writing

Every AI feature we've shipped at Luminary has taught us the same uncomfortable lesson: the model is the easy part. Wiring up an API that returns clever text takes an afternoon. Designing an interface where that text arrives at an unpredictable pace, is sometimes wrong with total confidence, and needs to be trusted just enough — but not too much — is the actual job. Most AI features don't fail because the model is bad. They fail because the UX around the model was designed as if the model were a database: ask, wait, get the right answer, render it. None of those four steps works the way it used to. This article is the playbook we wish we'd had — written for designers, and for the engineers who have to make the designs true.

Illustration: a user holding a large brass undo lever beside an embarrassed robot

Why AI broke the classic UX playbook

Traditional interface design rests on three assumptions so deep we rarely articulate them.

Determinism. The same input produces the same output. This is why we could design fixed layouts, write exact copy, and screenshot "the" result for a spec. An LLM-backed feature produces a different output every time — different length, different structure, sometimes a different answer. You are no longer designing a screen; you are designing a space of possible screens. Your layout has to survive a two-word reply and a fourteen-paragraph essay with three code blocks and a table.

Predictable latency. Classic guidance said responses under about 100ms feel instant, under a second keep the user's flow, and past ten seconds you lose them without feedback. AI responses routinely take 2–30 seconds and the variance is the problem: the same feature is snappy at 9am and sluggish at peak. You can't tune a spinner for that. You have to design for a latency distribution, not a latency.

Honest failure. When a form validation fails or a request 500s, the system knows it failed and can say so. An LLM's most dangerous failure mode is the opposite: it succeeds confidently at being wrong. There's no error code for a hallucinated citation. The interface has to carry the epistemics that the system itself cannot.

Everything below follows from taking those three breakages seriously instead of papering over them.

Streaming done well

Streaming is the single highest-leverage UX decision in an AI feature, and it's usually treated as a transport detail. It isn't. It's perceived performance, it's trust, and it's the user's escape hatch.

Token streaming, skeletons, or staged reveal — pick deliberately

There are three honest ways to fill the gap between request and result, and they suit different outputs:

PatternBest forFails when
Token streamingProse, chat, explanations — anything read linearlyStructured output (half-rendered JSON, jumping tables), anything the user will act on mid-stream
Skeleton / placeholderKnown layout, unknown content — cards, summaries with fixed fieldsOpen-ended output where you can't predict the shape
Staged revealMulti-step work: "Searching → Reading 4 sources → Drafting"There are no real stages and you're inventing theater

Token streaming works for prose because reading is sequential — the user consumes the beginning while the model writes the end, so waiting and reading overlap and total perceived latency collapses. It's actively wrong for structured output: never stream a half-formed table row or a partially valid JSON config the user might copy. For structured results, buffer and reveal complete units — a whole card, a whole row, a whole plan section. On our own project-scoping tool we made exactly this call: the model returns one complete structured plan via forced tool use, and the UI holds a designed loading state until the whole plan is valid, because a half-plan is worse than a short wait.

Staged reveal is the most underused of the three. When the system genuinely does phases — retrieval, then tool calls, then synthesis — naming the phases ("Reading your last 3 invoices…") converts dead air into evidence of work. The caveat is in the table: the stages must be real. Users forgive slowness; they don't forgive discovering the progress narration was fiction.

Perceived performance is the metric

Time-to-first-token matters more than time-to-completion, and time-to-first-meaningful-token matters more than either. Two implementation notes engineers should push back on designs to enable:

  • Front-load the useful part. Prompt the model to lead with the answer, not with throat-clearing ("Great question! There are several factors…"). The first streamed sentence is your effective load time.
  • Stabilize the layout. Streaming text that reflows the whole page — pushing buttons around, shifting scroll position — trades one kind of jank for another. Reserve space, pin the composer, and only autoscroll while the user is already at the bottom. The moment they scroll up to re-read, stop following the stream. Getting yanked back down while reading is one of the fastest ways to make streaming feel hostile.

Stop and regenerate are not optional

A streaming response without a Stop button is a modal dialog you can't dismiss. The user can see, two lines in, that the answer is going the wrong way — let them cut it off. Stop should be adjacent to the stream, instant, and should leave the partial text in place (they may still want it) with a clear "stopped" state.

Regenerate is the concession non-determinism demands: since another roll of the dice genuinely can produce a better answer, expose the reroll. Pair it with lightweight feedback (the thumbs, a "what was wrong?" affordance) so regeneration isn't just gambling — it's signal you can use to improve prompts and evals later.

Designing for uncertainty

The system will be wrong sometimes. The design question is not "how do we hide that" but "how do we make wrongness cheap to catch."

Confidence signals that mean something

Resist the urge to slap a percentage on everything. LLMs don't produce calibrated confidence scores, and a fabricated "87% confident" badge is worse than nothing — it's fake precision that trains users to trust a number nobody computed. Honest alternatives:

  • Hedged language in the output itself, prompted deliberately: "Based on the two documents you uploaded…" scopes the claim to its actual evidence.
  • Categorical signals where you have real data — "found in your docs" vs. "general knowledge" is a distinction the system can actually make, and it's more actionable than any percentage.
  • Structural honesty: if retrieval returned nothing relevant, say "I couldn't find this in your data" instead of letting the model freewheel.

Citations are a verification affordance, not decoration

The pattern Perplexity mainstreamed — inline numbered sources — works because it converts "trust me" into "check me." But citations only build trust if they're load-bearing: link to the specific passage, not the homepage; show enough source context on hover that verification takes two seconds, not a tab-switch and a Ctrl+F. A citation that resolves to something vaguely related is worse than no citation, because users spot-check early on, and the first bad link poisons every future one.

Disclaimers that work vs. liability boilerplate

"AI can make mistakes. Check important info." — the ChatGPT-style footer — is legally sensible and behaviorally almost inert. Persistent, generic, always-on warnings get banner blindness within a session. What actually changes behavior:

  • Proximity: the caveat sits on the claim, not the page. A flag on the specific figure the model computed beats a footer under the whole conversation.
  • Specificity: "This summary may omit clauses — 3 pages couldn't be parsed" is a disclaimer someone acts on. "AI-generated content may be inaccurate" is not.
  • Consequence-gating: reserve interruptive warnings for consequential moments (sending, publishing, executing), where attention is already high. Warn rarely and it means something; warn always and it means nothing.

Keep the boilerplate footer if legal wants it. Just don't confuse it with design.

Input UX: beyond the chat box

The empty text box is the AI era's blank-page problem. "Ask me anything" is a burden dressed up as freedom — the user now has to figure out what the system can do, how to phrase it, and what a good request even looks like.

Suggested prompts are the cheapest fix and most teams do them lazily. Good suggestions are a capability map: three or four examples chosen to span what the feature does ("Summarize this contract's termination clauses" teaches more than "Ask about your document"). Rotate them by context; retire them once the user demonstrates fluency.

Structured inputs beat free text whenever the task has known shape. If your feature needs a tone, a length, and a target audience, those are a segmented control, a slider, and a select — not three things the user must remember to type. The model still gets a prompt; the user gets a form they can't get wrong.

Hybrid forms-plus-chat is the pattern we reach for most in client work: structure for the knowable, free text for the rest. A travel request has dates and a budget (fields) and "somewhere quiet with good food" (text). The form constrains the model's job; the text box keeps the expressiveness that justified using a model at all.

And sometimes chat is simply the wrong interface. Our test: if the user's intent is selection or refinement rather than specification, don't make them write. Nobody wants to type "make the second paragraph more formal" when selecting the paragraph and clicking "More formal" is right there — this is why inline, contextual AI actions (the pattern popularized by tools like Notion and Linear, and by Copilot's ghost-text completions in the editor) so often beat a chat sidebar. Chat earns its place when intent is genuinely open-ended, multi-turn, and hard to enumerate. When you can enumerate it, enumerate it.

Trust and control

Trust in AI features isn't built by accuracy alone — it's built by recoverability. Users extend trust in proportion to how cheap mistakes are.

Undo everything undoable. If the AI renames files, rewrites a paragraph, or recategorizes records, the reverse operation should be one click and clearly advertised before the action runs. "You can undo this" changes the user's willingness to try at all.

Preview before irreversible. Anything that can't be undone — sending an email, charging a card, deleting data, posting publicly — gets a preview step where the AI's proposed action is shown as editable draft, not done deal. The draft-for-review pattern (compose, show, let the human edit, then send) is the single most trust-preserving pattern in agentic UX, and it costs one screen.

Human-in-the-loop is a dial, not a switch. Start new users at "approve every action," and let demonstrated reliability earn escalating autonomy: approve each step → approve batches → notify after the fact → fully autonomous with an audit log. The audit log is non-negotiable at every level — "what did the system do on my behalf, and when" must always have an answer. Autonomy granted by default is autonomy the user never consented to.

Loading and error states are the design

In an AI feature, the loading state isn't an edge case — it's where users spend a meaningful fraction of every interaction. Design it with the same care as the success state:

  • Acknowledge instantly. Something must change within ~100ms of submission, even if it's just the composer locking and a status appearing. The dead second before the spinner is where users double-click.
  • Escalate honestly. A quiet indicator for the first seconds; a named stage ("Analyzing 12 pages…") as it stretches; past a reasonable ceiling, an honest "this is taking longer than usual" with a cancel. Silence past ten seconds reads as a crash.
  • Fail with a next step. Every error state needs: what happened (in human terms), what survived (their input, above all — losing a carefully written prompt is unforgivable), and what to do next (retry, edit, or a non-AI fallback). Our own contact form degrades to a pre-filled mailto: when its endpoint is missing; the AI equivalent is offering the manual path — the plain search, the empty template — when the model is down. An AI feature with no non-AI fallback is a single point of failure you chose on purpose.

Accessibility: streaming is hostile by default

A token stream is dozens of DOM mutations per second. For a screen reader user, a naive implementation is either total silence or an unusable firehose of half-words. Neither is acceptable, and the fix is design work, not just ARIA sprinkles.

The core mechanism is a polite live region — but never wired to every token:

<!-- Announce meaningful units, not tokens -->
<div aria-live="polite" aria-atomic="false" class="sr-only" id="stream-announcer"></div>
// Buffer the stream; announce at sentence/paragraph boundaries.
let buffer = "";
function onToken(token) {
  renderToken(token);            // visual stream stays token-level
  buffer += token;
  if (/[.!?]\s$/.test(buffer)) { // flush on sentence end
    announcer.textContent = buffer;
    buffer = "";
  }
}

The pattern: the visual layer streams tokens, while the announced layer flushes complete sentences to aria-live="polite" so the screen reader queues coherent chunks instead of stuttering fragments. Also announce state transitions ("Generating response", "Response complete", "Generation stopped") — visually those are implied by the animation; aurally they don't exist unless you say them.

Beyond the live region: Stop and Regenerate must be reachable by keyboard while streaming (don't trap focus in the growing text); don't move focus into the response mid-stream; and honor reduced motion for the caret pulses and shimmer effects — which brings us to motion.

Dark patterns: the AI-specific ones

Some of these are old dark patterns in new clothes; some are genuinely new. We decline to ship all of them.

  • Fake typing indicators and artificial delays. Buffering a complete response and dribbling it out to seem "thoughtful" — or showing a typing animation while nothing is computing — is lying with motion. Users eventually notice the rhythm is fake, and the discovery costs more trust than the theater ever bought.
  • Hiding the AI-ness. Passing model output as human work — an unlabeled "support agent," AI-written reviews, auto-generated summaries presented as editorial — removes the user's ability to calibrate trust. Label generated content at the point of consumption, not in a settings page. (Regulation is heading this way regardless; design ahead of it.)
  • Fake progress and fabricated stages. "Analyzing your data…" while the request sits in a queue is the staged-reveal pattern weaponized.
  • Confidence theater. Invented precision — the uncalibrated percentage, the five-star "accuracy" badge with no ground truth behind it.
  • Consent laundering. Burying "your inputs train our models" in a ToS while the UI cheerfully says "your data is private." Data-use disclosure belongs where the data is entered.

The common thread: each pattern spends long-term trust to buy a short-term impression. It's a bad trade every time.

Motion that survives reduced motion

AI interfaces lean hard on motion — pulsing carets, shimmer skeletons, streaming text itself is animation. Every one of those needs a reduced-motion story, and "delete all feedback" is not it. prefers-reduced-motion means reduce, not remove: users still need to know the system is working; they need it without the vestibular assault.

.stream-caret { animation: pulse 1s ease-in-out infinite; }
.skeleton    { animation: shimmer 1.6s linear infinite; }

@media (prefers-reduced-motion: reduce) {
  .stream-caret,
  .skeleton {
    animation: none;
  }
  /* Replace motion with state, not with nothing */
  .skeleton    { background: var(--surface-muted); }
  .stream-caret::after { content: "…"; }
  .is-generating .status-dot { opacity: 1; } /* static indicator */
}

Our working principles: prefer opacity and color changes over translation (cross-fades survive reduced-motion far better than slides); make every animation informative (motion that encodes state gets a static equivalent; motion that's pure garnish just goes); and test the reduced-motion path as a first-class variant, not a regression suite afterthought. On our own site, every canvas effect and scroll reveal has a reduced-motion and no-JS fallback — the content is never hostage to the animation. The same bar applies doubly to AI features, where the animation is the primary feedback channel.

The shipping checklist

Before an AI feature goes out the door, we walk this list:

Streaming & latency

  • First visible feedback within ~100ms of submit
  • Streaming strategy matches output shape (tokens for prose, complete units for structure, real stages for multi-step)
  • Stop works instantly and preserves partial output; Regenerate exists
  • Layout is stable mid-stream; autoscroll releases when the user scrolls up
  • Slow path (>10s) has honest messaging and a cancel

Uncertainty & trust

  • No fabricated confidence numbers anywhere
  • Citations link to specific, verifiable passages
  • Caveats are specific and proximate, not just a global footer
  • Generated content is labeled where it's consumed
  • Irreversible actions have preview-and-edit; reversible ones have visible undo
  • There's an audit trail of actions taken on the user's behalf

Failure & fallback

  • Every error state preserves the user's input
  • Model-down has a designed fallback, not a dead end
  • Empty/ambiguous retrieval produces "I don't know," not improvisation

Input

  • Suggested prompts map the feature's actual capabilities
  • Everything with known shape is a structured input, not prose
  • You asked "should this be chat at all?" and wrote down the answer

Accessibility & motion

  • Live region announces sentences and state changes, not tokens
  • Stop/Regenerate keyboard-reachable during generation; focus never hijacked
  • Reduced-motion variant tested; state feedback survives it

Takeaways

  • AI broke three UX assumptions — determinism, predictable latency, honest failure. Design for output ranges, latency distributions, and confident wrongness, or the interface will lie on your behalf.
  • Stream prose token-by-token; never stream half-formed structure. Buffer structured output into complete, valid units.
  • Stop and Regenerate are core affordances, not extras. A stream you can't stop is a modal you can't dismiss.
  • Skip fake confidence scores. Use citations that resolve to specific passages, caveats attached to specific claims, and honest "not found in your data" states.
  • The chat box is often the wrong interface. Structure what you can enumerate; save free text for genuinely open-ended intent; prefer inline contextual actions for selection-and-refinement tasks.
  • Trust scales with recoverability: undo, preview-before-irreversible, and human approval that relaxes gradually — always with an audit log.
  • Loading and error states get success-state-level design effort. Preserve input at all costs; always offer a non-AI path.
  • Announce sentences, not tokens, to aria-live="polite"; keep controls reachable mid-stream; treat reduced motion as a first-class variant that replaces motion with state.
  • Never fake the machine: no artificial typing, no fabricated stages, no hidden AI-ness. Every theatrical trick spends trust you can't buy back.

Enjoyed the read? We build this stuff for clients too.

Start a project