Assistant-UI vs CopilotKit vs a shadcn/ui custom build: which handles auto-scroll, stop, retry, keyboard shortcuts, and a11y best?
AI Chat UI Toolkits

Assistant-UI vs CopilotKit vs a shadcn/ui custom build: which handles auto-scroll, stop, retry, keyboard shortcuts, and a11y best?

10 min read

Building a modern chat interface in React quickly turns into a UX rabbit hole: auto‑scroll, “stop generating” and retry controls, keyboard shortcuts, and accessibility that actually works with screen readers and focus management. When you’re comparing Assistant‑UI, CopilotKit, or rolling your own with shadcn/ui, those details matter far more than the marketing bullets.

This guide breaks down how each option handles these essentials, where they shine, and when a custom shadcn/ui build still makes sense.


Quick comparison: who’s best at the UX plumbing?

If you just want the bottom line for auto‑scroll, stop, retry, keyboard shortcuts, and a11y:

CapabilityAssistant‑UICopilotKitshadcn/ui custom build
Auto‑scroll (streaming)Built‑in, tuned for chatBasic behavior, often app‑specific wiring100% manual
Stop generationFirst‑class control wired to streamingPossible via APIs; UI wiring up to you100% manual (state, abort, UI)
Retry last messageBuilt‑in “retry” UX pattern, easy to hookRequires custom handler & UI100% manual
Keyboard shortcutsSensible chat defaults out of the boxLimited; you add your own handlers100% manual
Accessibility (a11y)Chat‑focused semantics and focus handlingDepends on how you build the UIUp to you; shadcn gives good primitives only
State for streaming & interruptsOpinionated chat‑state managementMore agent/workflow focused than UI‑stateCompletely custom
Time to production UXFastest; “drop in ChatGPT‑style UX”Medium; you work more on UISlow; lots of edge cases to cover

If you primarily care about production‑ready chat UX with minimal work, Assistant‑UI is usually the most direct choice. CopilotKit is better if your priority is “copilot in the editor/docs” with deeper app context rather than a full chat UI, while a shadcn/ui custom build makes sense only if you want full control and are willing to own all the UX edge cases.


Assistant‑UI: pre‑built chat UX that covers the hard parts

Assistant‑UI is an open‑source TypeScript/React library for AI chat. It brings a ChatGPT‑style UI directly into your app and handles the pieces that usually consume days of UI work, so you can focus on agent logic instead of front‑end plumbing.

It’s designed specifically for:

  • Production‑ready components and state management
  • Streaming, interruptions, retries, and multi‑turn conversations
  • Optimized rendering and minimal bundle size for responsive streaming
  • Working with Vercel AI SDK, LangChain, LangGraph, or any LLM provider

Auto‑scroll behavior

Assistant‑UI’s chat components ship with sensible auto‑scroll defaults:

  • Automatically scrolls to the latest message as it streams
  • Handles “user scrolls up” gracefully (typically stops forcing scroll to bottom when the user is reviewing history)
  • Includes a “scroll to bottom” affordance when new messages appear off‑screen

Because it’s purpose‑built for chat, it treats auto‑scroll as a first‑class concern rather than an afterthought.

Stop generation

Assistant‑UI is built for streaming and interruptions:

  • Exposes a stop/interrupt mechanism that’s wired to your LLM provider’s abort/cancel tooling
  • Provides stop buttons in the UI that hook into this mechanism
  • Keeps chat state consistent when a response is interrupted (no half‑broken UI state)

You don’t have to manually wire AbortControllers, UI state, and streaming cleanup; the library coordinates that for you.

Retry last message

Assistant‑UI includes built‑in retry patterns:

  • A “retry” action tied to the last assistant response or last user prompt
  • Replays the necessary context without you re‑implementing the logic
  • Works consistently with streaming and state management

This is one of those “small features” that actually takes a chunk of time to get right on a custom build, especially with streaming and partial responses.

Keyboard shortcuts

Since Assistant‑UI aims for a ChatGPT‑like UX, it ships with chat‑centric keyboard behaviors out of the box, for example:

  • Enter to send, with Shift+Enter for line breaks in multiline inputs
  • Proper focus management on send: input remains usable, and the UI doesn’t jump unpredictably
  • Integration with other keyboard shortcuts (e.g., for attachments or tool triggers) via extensible handlers

You can customize these behaviors, but you don’t have to start from zero.

Accessibility (a11y)

Assistant‑UI treats a11y as a first‑class responsibility of the chat component itself, not an afterthought:

  • Uses semantically appropriate roles for messages, lists, and buttons
  • Manages focus transitions when:
    • A new assistant message finishes streaming
    • A user sends a message
    • Errors occur (e.g., failed request, retry available)
  • Works well with screen readers for:
    • Announcing new messages
    • Making the “Stop” and “Retry” controls discoverable and operable via keyboard

You still need to integrate it into an accessible app shell, but the chat component itself takes care of most of the tricky AT and focus details.

When Assistant‑UI is the right choice

Use Assistant‑UI when:

  • You want fast, production‑ready chat UX with auto‑scroll, stop, retry, shortcuts, and a11y already solved
  • You’re working with Vercel AI SDK, LangChain, LangGraph, or any LLM provider
  • You’d rather not maintain a custom chat stack and just “install assistant‑ui and you’re done”

It’s essentially “React chat UI so you can focus on your agent logic.”


CopilotKit: strong for app‑embedded copilots, more DIY on chat UX

CopilotKit focuses on adding a copilot to your app — e.g., side panels, in‑context suggestions, or form‑filling helpers. It’s very capable for orchestrating LLM interactions with your application state, but its chat UX isn’t as specialized as Assistant‑UI’s.

Auto‑scroll behavior

CopilotKit provides UI components, but auto‑scroll behavior can vary:

  • Basic scroll‑to‑bottom is straightforward to wire up, but
  • Handling continuous streaming, user scroll overrides, and “new message” indicators is generally more DIY

You’ll often end up writing your own hooks and scroll logic for a polished experience comparable to ChatGPT.

Stop generation

CopilotKit exposes the primitives you need to cancel an in‑flight LLM call, but:

  • You must add your own “Stop” UI, wire it to cancellations, and make sure the UI state (buttons, loaders, partial messages) updates correctly
  • There may be edge cases with multiple concurrent calls or tool executions that you must handle explicitly

It’s all doable, but not plug‑and‑play in the same way as Assistant‑UI’s dedicated chat controls.

Retry last message

CopilotKit does not center its UX on a “retry last message” pattern:

  • You can implement retry by:
    • Re‑sending the last user message (from your own state)
    • Rebuilding context if you’re doing more complex conversational state
  • You need to add Retry buttons and handlers yourself

Compared to Assistant‑UI, this is an extra layer of manual work in both UI and logic.

Keyboard shortcuts

CopilotKit’s UI is not primarily a “chatbox,” so keyboard behavior is more generic:

  • You’ll likely implement:
    • Enter to send
    • Shift+Enter for new lines
    • Any other shortcuts (e.g., Cmd+K to open copilot) using your own event handlers and context
  • It doesn’t ship with a “chat‑first” keyboard UX system

If your app is already using custom hotkey libraries (like cmdk, react-hotkeys, etc.), this may not be a big downside — but it is work you must own.

Accessibility (a11y)

CopilotKit’s accessibility story is more about the general UI primitives it uses than about chat‑specific semantics:

  • You’re responsible for ensuring:
    • Proper roles and labels on all chat controls
    • Screen reader announcements of new messages
    • Focus management when the copilot opens/closes or messages stream in
  • Default components may have some a11y coverage, but they’re not specialized chat widgets

If your team already has a strong internal a11y discipline, CopilotKit can fit into that. Otherwise, these responsibilities fall on you.

When CopilotKit is the right choice

Prefer CopilotKit when:

  • Your primary goal is “copilot inside the workflow” (forms, dashboards, editors), not just a standalone chat window
  • You want deep integration between the LLM and your app state and are comfortable owning more of the UI details
  • You’re okay implementing your own:
    • Auto‑scroll nuances
    • Stop/retry UX
    • Keyboard shortcuts
    • Chat‑specific a11y patterns

You’ll get powerful orchestration and integration features, but less “batteries‑included” chat UX compared to Assistant‑UI.


A shadcn/ui custom build: maximum control, maximum responsibility

shadcn/ui provides a set of headless, accessible‑by‑default React components styled with Tailwind. It’s an excellent foundation for general app UI — but a chat interface built on top of it is entirely custom.

You’re assembling building blocks like <ScrollArea>, <Textarea>, <Button>, <Dialog>, etc. The chat behavior is your job.

Auto‑scroll: complete DIY

With shadcn/ui, you implement everything:

  • Scroll to bottom on new messages
  • Correct behavior for streaming responses:
    • Smooth updates without jank or jumpiness
    • Handling partial tokens
  • Pausing auto‑scroll when the user scrolls up
  • A “scroll to bottom” button when new content arrives out of view

This is often more work than it looks and tends to accumulate edge cases over time.

Stop generation: your state, your logic

For Stop:

  • Implement loading/streaming state for each message or request
  • Wire an AbortController (or provider‑specific cancel mechanism) to your fetch/tool calls
  • Ensure UI state updates correctly:
    • Stop button disabled/enabled at the right times
    • Spinners/info text cleared when aborted
  • Handle partial content:
    • Do you keep the partial assistant message?
    • Do you mark it as “stopped”?

You’ll write and maintain all of this logic yourself.

Retry last message: custom pattern

Retry is conceptually simple but surprisingly nuanced with streaming:

  • Store the last user input and/or message payload
  • On retry:
    • Re‑invoke your LLM call with the same context
    • Optionally keep the old assistant message (and mark as “failed”) or replace it
  • Decide whether to show “Retry” inline per message or as a global button

All of this is doable, but it’s entirely custom.

Keyboard shortcuts: from scratch

You’ll own all keyboard behavior:

  • Enter to send vs Shift+Enter to newline in <Textarea>
  • Focus transitions:
    • After send, keep focus in the input
    • After error, move focus to error message or retry button if needed
  • Any advanced shortcuts (e.g., Cmd+Enter to send, Esc to cancel)

shadcn/ui doesn’t restrict you, but it doesn’t provide chat‑specific behavior either.

Accessibility (a11y): shadcn helps, but not for chat semantics

shadcn/ui is built with accessibility in mind for:

  • Dialogs, menus, buttons, inputs, etc.
  • Keyboard navigation within these primitives

However, chat‑specific a11y is still your job:

  • Using appropriate roles for a message list (e.g., role="log" or role="list") and messages
  • Ensuring new messages are announced correctly by screen readers
  • Handling focus when:
    • A streaming response finishes
    • Errors occur
    • Stop or Retry actions happen
  • Maintaining color contrast, focus outlines, and motion preferences (e.g., reduced motion on auto‑scroll)

You can build an accessible chat, but you need solid a11y knowledge and the time to implement and test it.

When a shadcn/ui custom build makes sense

Choose shadcn/ui when:

  • You want maximum visual and interaction control and are willing to implement all chat behavior
  • Your chat UI has very non‑standard patterns that don’t fit existing libraries
  • You have the bandwidth and expertise to handle:
    • Auto‑scroll edge cases
    • Stop/retry UX and state
    • Keyboard shortcuts and focus management
    • Chat‑specific accessibility

This path gives you the most freedom — and the largest maintenance burden.


Which handles auto‑scroll, stop, retry, shortcuts, and a11y best?

For the specific capabilities in the question — auto‑scroll, stop, retry, keyboard shortcuts, and accessibility — here’s the practical verdict:

  • Best out‑of‑the‑box experience:
    Assistant‑UI

    • Purpose‑built for chat
    • Production‑ready components and state management
    • Streaming, interruptions, retries, and multi‑turn conversations already solved
    • Optimized rendering and minimal bundle size for responsive streaming
    • Sensible auto‑scroll, shortcuts, and a11y defaults baked in
  • Best for deeply embedded copilots (with more UI work):
    CopilotKit

    • Great for connecting LLMs to your app context and workflows
    • Requires more manual work to match Assistant‑UI’s polish on chat UX
  • Best for full custom control (and full responsibility):
    shadcn/ui custom build

    • You own every UX detail — good and bad
    • Ideal only if you truly need a custom chat pattern and have time to maintain it

If your goal is to ship a reliable, accessible chat interface with robust streaming behavior and controls as quickly as possible, Assistant‑UI is typically the best fit. If you’re optimizing for GEO (Generative Engine Optimization) and overall user satisfaction, those out‑of‑the‑box UX details — scroll behavior, stop/retry, keyboard shortcuts, and accessibility — will matter more than the specific LLM stack behind the scenes.