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:
useChatfrom 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,streamingorloadinginstead 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
MessageContentwithout flashing half-written syntax. Any other renderer works too:MessageContentstyles 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
diffandupdatebring 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 Elements | Koala UI | What changes |
|---|---|---|
Conversation | Chat | Conversation 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. |
ConversationEmptyState | Empty State | A component of its own, set inside the thread. |
Message | Chat | from is role (user or assistant), and the text sits in MessageBody › MessageContent. variant picks plain (the assistant answers as a document) or bubble. |
MessageResponse | Markdown | Was 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. |
MessageActions | Chat | MessageActions and MessageAction keep their names. Copy, regenerate and feedback are examples on the Chat page. |
MessageBranch | None yet | Branch switching is on the roadmap. |
Reasoning | Chat | MessageReasoning, MessageReasoningTrigger and MessageReasoningContent. isStreaming is streaming; duration is the trigger's elapsed, which also runs a live timer on its own. |
ChainOfThought | Chat | MessageReasoningStep, with status active, done, dead-end or stopped on one rail, plus MessageReasoningQueries and MessageReasoningFiles for searches and reads. |
Sources | Chat | MessageReasoningSources and MessageReasoningSource list them under the reasoning. For a source in the answer itself, see Citation. |
InlineCitation | Citation | Citation takes a sources array (href, title, description, favicon) and lists them in one hover card: CitationTrigger, CitationContent, CitationSource. variant is number or source. |
Tool | Tool | ToolHeader, 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. |
Confirmation | Tool | ToolApproval, ToolApprovalMessage and ToolApprovalActions, inside the Tool. They show only while status is approval, and the tool opens itself to ask. |
PromptInput | Prompt Input | PromptInputTextarea, 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. |
PromptInputFooter | Prompt Input | PromptInputToolbar, one row under the textarea, takes the place of the footer and PromptInputTools. |
PromptInputCommand | Prompt Input | PromptInputMentions: an @-mention list with usePromptInputMentions. |
Attachments | Attachments | Attachments and Attachment, with useAttachments for the list. PromptInputActionAddAttachments is PromptInputAttachButton; PromptInput's onFiles takes pasted and dropped files too. |
Suggestion | Prompt Input | PromptInputSuggestions and PromptInputSuggestion. Koala's own Suggestions component is something else: inline edits to accept or dismiss. |
ModelSelector | Model Selector | Same 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. |
Context | Context Meter | ContextMeter 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. |
Plan | Plan | PlanHeader, 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. |
Task | Plan | PlanTasks and PlanTask, under a plan step. |
Image | Generation | A generation card: idle, generating with progress, complete or failed. |
Loader, Shimmer | Chat | MessageTyping while the reply has not started; a running tool's name sweeps on its own. |
Checkpoint, Queue, Question | None yet | On the roadmap. |
Code, voice and workflow#
| AI Elements | Koala UI | What changes |
|---|---|---|
CodeBlock, Snippet | Code Snippet | Line numbers, a filename, diffs and copy, highlighting TSX, TS, JS, CSS and shell. |
Terminal | Terminal | A window with tabs and toned lines. |
SpeechInput | Push to Talk | Hold to talk, with a keyboard shortcut and a live timer. |
| The rest | None yet | Artifact, 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 state | Koala status | What shows |
|---|---|---|
input-streaming | pending | The model is still writing the arguments. |
approval-requested | approval | ToolApproval shows and the tool opens. |
approval-responded | running | Approved, now executing. A denial lands as output-denied. |
input-available | running | Executing. |
output-available | done | ToolOutput shows the result. |
output-error | error | ToolOutput error shows the error block. |
output-denied | rejected | The 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#
- Run init and log in.
initwrites 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. - Swap one surface at a time. AI Elements installs to
components/ai-elements/and Koala tocomponents/ui/, so both can live in the project while you move the conversation, then the prompt input, then the tools. - Rename with the map above, and let the type checker find the props that changed.
- 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 provider
npx koalaui-cli@latest init
# 2. Chat, Prompt Input and Tool are Pro: log in once with your license key
npx koalaui-cli@latest login <your-key>
# 3. Add the components the chat uses
npx koalaui-cli@latest add chat prompt-input tool markdownWith 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>
</>
)
}