Migrating from AI Elements

Vercel deprecates AI Elements at the end of November 2026. Koala UI covers the chat, reasoning, tool, prompt and model pieces with the same shape of parts, themed, dense and kept up to date. Here is where each one lands.

What carries over#

  • The source lives in your repo under components/ui/, installed by a CLI, on Tailwind CSS and Radix.
  • Composition by named parts: <Tool><ToolHeader>, <PromptInput><PromptInputTextarea>. Many keep their AI Elements names.
  • Your data layer. Koala renders state, it does not fetch it: useChat from the AI SDK, assistant-ui, LangGraph or your own stream all drive the same props.

What changes#

  • Status is a plain prop. Components take status, streaming or loading instead of the AI SDK’s types, so nothing is tied to one SDK version. The tool states map one to one, below.
  • Markdown streams cleanly. Markdown renders the reply inside MessageContent without flashing half-written syntax. Any other renderer works too: MessageContent styles whatever you put in it.
  • Themes and density. Four themes, eight accents and a compact density reach the AI components too, where AI Elements inherits one shadcn palette. See Theming.
  • Updates that keep your edits. The CLI’s diff and update bring fixes in without overwriting the files you changed. See Installation.
  • Most AI components are Pro. Code Snippet and the foundation components are free; markdown, chat, prompt input, tool, plan and the rest come with a one-time license.

Chatbot components#

Every chatbot component in AI Elements and where it lands. A Koala name links to its docs page, where the full API and examples live.

AI ElementsKoala UIWhat changes
ConversationChatConversation takes the messages directly, without ConversationContent. It sticks to the bottom while a reply streams (stickToBottom, on by default), and ConversationScrollButton comes back when the reader scrolls up.
ConversationEmptyStateEmpty StateA component of its own, set inside the thread.
MessageChatfrom is role (user or assistant), and the text sits in MessageBody › MessageContent. variant picks plain (the assistant answers as a document) or bubble.
MessageResponseMarkdownWas Response. <Markdown>{text}</Markdown> inside MessageContent: it takes the message's streaming, keeps half-written syntax off screen and puts the caret after the last word.
MessageActionsChatMessageActions and MessageAction keep their names. Copy, regenerate and feedback are examples on the Chat page.
MessageBranchNone yetBranch switching is on the roadmap.
ReasoningChatMessageReasoning, MessageReasoningTrigger and MessageReasoningContent. isStreaming is streaming; duration is the trigger's elapsed, which also runs a live timer on its own.
ChainOfThoughtChatMessageReasoningStep, with status active, done, dead-end or stopped on one rail, plus MessageReasoningQueries and MessageReasoningFiles for searches and reads.
SourcesChatMessageReasoningSources and MessageReasoningSource list them under the reasoning. For a source in the answer itself, see Citation.
InlineCitationCitationCitation takes a sources array (href, title, description, favicon) and lists them in one hover card: CitationTrigger, CitationContent, CitationSource. variant is number or source.
ToolToolToolHeader, ToolContent, ToolInput and ToolOutput keep their names. The state moves from ToolHeader to Tool's status (mapped below). ToolIcon, ToolName, ToolSummary and ToolMeta fill the header.
ConfirmationToolToolApproval, ToolApprovalMessage and ToolApprovalActions, inside the Tool. They show only while status is approval, and the tool opens itself to ask.
PromptInputPrompt InputPromptInputTextarea, PromptInputSubmit and PromptInputButton keep their names. PromptInputSubmit's status is PromptInput's loading: while it is on, Submit becomes Stop and calls onStop. onSubmit receives the text.
PromptInputFooterPrompt InputPromptInputToolbar, one row under the textarea, takes the place of the footer and PromptInputTools.
PromptInputCommandPrompt InputPromptInputMentions: an @-mention list with usePromptInputMentions.
AttachmentsAttachmentsAttachments and Attachment, with useAttachments for the list. PromptInputActionAddAttachments is PromptInputAttachButton; PromptInput's onFiles takes pasted and dropped files too.
SuggestionPrompt InputPromptInputSuggestions and PromptInputSuggestion. Koala's own Suggestions component is something else: inline edits to accept or dismiss.
ModelSelectorModel SelectorSame names for Trigger, Content, Input, List, Group, Item, Logo and Name, with value and onValueChange on the root. Capabilities and Meta are Koala's additions.
ContextContext MeterContextMeter with used and max (were usedTokens and maxTokens) and warnAt and criticalAt thresholds. Input, output, reasoning and cache usage are one ContextMeterRow each. Cost is a row value you compute.
PlanPlanPlanHeader, PlanTitle, PlanDescription and PlanFooter keep their names. isStreaming is status="drafting", then review, running, complete or failed; the steps are PlanSteps › PlanStep, each with a status.
TaskPlanPlanTasks and PlanTask, under a plan step.
ImageGenerationA generation card: idle, generating with progress, complete or failed.
Loader, ShimmerChatMessageTyping while the reply has not started; a running tool's name sweeps on its own.
Checkpoint, Queue, QuestionNone yetOn the roadmap.

Code, voice and workflow#

AI ElementsKoala UIWhat changes
CodeBlock, SnippetCode SnippetLine numbers, a filename, diffs and copy, highlighting TSX, TS, JS, CSS and shell.
TerminalTerminalA window with tabs and toned lines.
SpeechInputPush to TalkHold to talk, with a keyboard shortcut and a live timer.
The restNone yetArtifact, WebPreview, Sandbox and the other code parts, the voice selectors and the workflow canvas have no counterpart yet. Keep them from AI Elements for now: their source is in your repo and works next to Koala.

Tool states#

The AI SDK’s tool part state and Koala’s status. Map it once and pass it to Tool; the output and the approval appear on their own.

AI SDK stateKoala statusWhat shows
input-streamingpendingThe model is still writing the arguments.
approval-requestedapprovalToolApproval shows and the tool opens.
approval-respondedrunningApproved, now executing. A denial lands as output-denied.
input-availablerunningExecuting.
output-availabledoneToolOutput shows the result.
output-errorerrorToolOutput error shows the error block.
output-deniedrejectedThe user said no.
import type { ToolState } from "@/components/ui/tool"const STATUS = {  "input-streaming": "pending",  "approval-requested": "approval",  "approval-responded": "running",  "input-available": "running",  "output-available": "done",  "output-error": "error",  "output-denied": "rejected",} as const satisfies Record<string, ToolState>// <Tool status={STATUS[part.state]}>…</Tool>

Moving a project over#

  1. Run init and log in. init writes the tokens, the helpers and the theme provider. If the project started from shadcn/ui, clear its color blocks first, as Migrating from shadcn/ui describes.
  2. Swap one surface at a time. AI Elements installs to components/ai-elements/ and Koala to components/ui/, so both can live in the project while you move the conversation, then the prompt input, then the tools.
  3. Rename with the map above, and let the type checker find the props that changed.
  4. Keep what has no counterpart yet. The AI Elements source is in your repo, so its code, voice and workflow components keep working after the deprecation.
# 1. Tokens, the lib helpers and the theme providernpx koalaui-cli@latest init# 2. Chat, Prompt Input and Tool are Pro: log in once with your license keynpx koalaui-cli@latest login <your-key># 3. Add the components the chat usesnpx koalaui-cli@latest add chat prompt-input tool markdown

With the AI SDK#

A useChat chat on Koala: the SDK keeps the messages and the stream, Koala draws them.

"use client"import { useChat } from "@ai-sdk/react"import { Conversation, Message, MessageBody, MessageContent } from "@/components/ui/chat"import {  PromptInput,  PromptInputSubmit,  PromptInputTextarea,  PromptInputToolbar,} from "@/components/ui/prompt-input"import { Markdown } from "@/components/ui/markdown"export function Chat() {  const { messages, sendMessage, status, stop } = useChat()  const busy = status === "submitted" || status === "streaming"  const last = messages.at(-1)?.id  return (    <>      <Conversation>        {messages.map((message) => (          <Message key={message.id} role={message.role === "user" ? "user" : "assistant"}>            <MessageBody>              <MessageContent streaming={busy && message.id === last && message.role === "assistant"}>                {message.parts.map((part, i) =>                  part.type === "text" ? <Markdown key={i}>{part.text}</Markdown> : null,                )}              </MessageContent>            </MessageBody>          </Message>        ))}      </Conversation>      <PromptInput loading={busy} onSubmit={(text) => sendMessage({ text })} onStop={stop}>        <PromptInputTextarea placeholder="Ask anything…" />        <PromptInputToolbar>          <PromptInputSubmit />        </PromptInputToolbar>      </PromptInput>    </>  )}

FAQ#

Vercel's site says AI Elements is moving to shadcn/ui and will be deprecated at the end of November 2026. The components you installed keep working, since their source is in your repo; they just stop getting fixes.