Before you start#
- The AI Elements site says the project is moving to shadcn/ui and will be deprecated at the end of November 2026. Its components keep working where they are installed; this guide is for the parts you want to replace.
- PixilUI items take plain props, not AI SDK message parts. You map each part to props in a line or two (examples below), which also means the items work with any SDK or your own stream.
- AI Elements lives in
components/ai-elements/, PixilUI incomponents/pixilui/. Neither overwrites the other. - PixilUI reads its own tokens from
foundation.css. Move a whole message or panel at a time so one surface never mixes the two looks.
What maps to what#
| AI Elements | PixilUI | What changes |
|---|---|---|
Tool and its parts |
tool-call-row |
One component for Tool, ToolHeader, ToolContent, ToolInput and ToolOutput. Map the part's state to status (see below), pass input, output and errorText as input, output and error. Write summary as what the call did in plain words. Wrap the calls of one message in ToolCallRowGroup. |
Confirmation and its parts |
approval-card |
Show what the agent will do as items rows with a risk. onApprove and onReject send the approval response; return the promise and the card shows pending, then folds into a receipt. |
Reasoning |
thinking-state |
isStreaming becomes done={!isStreaming}; the timer runs from startedAt. Pass short steps instead of the raw reasoning text, or keep Reasoning if you show the full text. |
Loader, Shimmer |
thinking-state |
While waiting for the first token: a line that says what the agent is doing, with a live timer, instead of a spinner. |
Response, MessageResponse |
streaming-text |
text and streaming; only new words animate and half-arrived words wait. It renders plain text with inline code, not full markdown: keep Response for answers with lists, tables or code blocks. |
Task, ChainOfThought, Plan |
run-timeline |
Steps with id, title, detail and status (pending, active, done, error, waiting). A waiting step holds an approval card. |
Conversation and the rest |
None yet | Keep AI Elements or shadcn/ui for Conversation, Message, PromptInput, CodeBlock, Sources, InlineCitation, WebPreview and Canvas. The roadmap lists what is planned. |
Steps#
1. Add the registry and the foundation
npx pixilui@latest initThis adds @pixilui next to @ai-elements in components.json, installs the foundation and writes the agent rules.
2. Add the items
npx pixilui@latest add tool-call-row approval-card thinking-state3. Map the message parts to props
Where you render a tool part today:
// Before
<Tool>
<ToolHeader type={part.type} state={part.state} />
<ToolContent>
<ToolInput input={part.input} />
<ToolOutput output={part.output} errorText={part.errorText} />
</ToolContent>
</Tool>
// After
import { ToolCallRow, type ToolCallRowValue } from "@/components/pixilui/tool-call-row";
const status = (state: string) => (state === "output-error" ? "error" : state === "output-available" ? "done" : "running");
<ToolCallRow
name={part.type.replace(/^tool-/, "")}
summary={describe(part)}
status={status(part.state)}
input={part.input as ToolCallRowValue}
output={part.output as ToolCallRowValue}
error={part.errorText}
/>describe is yours: one short past-tense line per tool ("Found 3 overdue invoices for Northwind"). It is the line people read, so it is worth writing.
For reasoning:
// Before
<Reasoning isStreaming={status === "streaming"}>
<ReasoningTrigger />
<ReasoningContent>{part.text}</ReasoningContent>
</Reasoning>
// After
<ThinkingState label="Reading the incident timeline" startedAt={startedAt} done={status !== "streaming"} />4. Replace the approval step
Where an AI Elements Confirmation asks before a tool runs, render an approval card and send the same response from its callbacks:
<ApprovalCard
title="Send 7 renewal reminders"
risk="medium"
items={[
{ label: "Audience", value: "7 customers with a card expiring in November" },
{ label: "Cost", value: "$0.42", numeric: true },
]}
onApprove={() => respond({ id: part.approval.id, approved: true })}
onReject={(note) => respond({ id: part.approval.id, approved: false, reason: note })}
/>respond is the approval call your SDK gives you; with the AI SDK it is the approval response helper from useChat. Return its promise so the card shows its own pending state.
5. Keep the rest
Leave Conversation, Message, PromptInput and the other parts with no match where they are. Remove an AI Elements file only when nothing imports it.
With an agent#
Paste this into Claude Code, Cursor or Codex:
Read https://pixilui.com/docs/migrate-from-ai-elements/markdown. In the chat page, replace the AI Elements Tool, Confirmation and Reasoning parts with PixilUI tool-call-row, approval-card and thinking-state using that mapping. Keep everything else. List what you changed.