Prodular UI

AI message parts

AI

Renders one Vercel AI SDK UIMessage with Prodular UI: text through Response, reasoning, every tool state, approvals wired to addToolApprovalResponse, sources and inline citations.

npx prodular-ui add ai-message-parts

With useChat

Your own layout around useChat. Map messages to AiMessageParts and pass addToolApprovalResponse. This demo streams a scripted answer from a docs route, no key needed.

Every tool state

Preparing, running, done, failed, denied and waiting for approval, straight from the part state. Approve or deny the last call.

Relay Copilot
Input
{
  "account": "Meridian Bank ****4417",
  "since": "2026-10-14"
}
Error
Bank connection expired on Oct 20. Reconnect it in Settings.
Input
{
  "customer": "Tidewater Energy",
  "to": "ap@tidewater.energy",
  "invoices": [
    "INV-2090"
  ]
}
Emails a customer on behalf of Halcyon Labs.

Anatomy

const { messages, status, addToolApprovalResponse } = useChat({  sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses,})
{messages.map((message) => (  <Message key={message.id} from={message.role}>    <MessageContent>      <AiMessageParts        message={message}        streaming={status === "streaming" && message.id === messages.at(-1)?.id}        tools={{ sendReminder: { title: "Send payment reminder", approval: { actionLabel: "Send reminder" } } }}        onApprovalResponse={addToolApprovalResponse}      />    </MessageContent>  </Message>))}

Guidance

Use it when

  • Inside your own chat layout built on useChat, when AI chat does not fit the page.
  • To render saved threads: pass a stored UIMessage and it renders the same way it streamed.
  • Inside a side panel, an inbox or an agent run, wherever an AI SDK message needs to show.

Avoid

  • Mapping message.parts by hand. Text grouping, citation numbering and tool states are already handled here.
  • Wrapping it in a card or a bubble. Assistant answers sit on the page; only user text gets a bubble, which it renders itself.
  • Setting streaming on every message. Only the last assistant message while status is streaming.

Rules for AI agents

Published in llms-full.txt, the registry and the MCP server, so coding agents follow them when they build with this component.

  • Render one AiMessageParts per UIMessage inside MessageContent. Pass streaming={status === "streaming" && message.id === lastMessage.id} so only the live message shows the caret and the reasoning timer.
  • Always pass onApprovalResponse={addToolApprovalResponse} from useChat, and set sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses on useChat. Without both, approvals never reach the server.
  • Pass tools keyed by the server tool name with a verb first title and an icon. For approval tools add approval.actionLabel that names the action, for example Send reminder.
  • In the route, keep sendReasoning and sendSources on in toUIMessageStream, and use thinking display summarized for Claude. Otherwise reasoning shows empty and sources never arrive.
  • Data parts render nothing by default. Render them with renderPart, which runs before the defaults for every part; return undefined to keep the default.
  • This item is free. Do not import Pro components into it. For the Pro Approval card, pass renderApproval, as AI chat does.

API

AiMessageParts

PropTypeDefaultDescription
messageUIMessageA message from useChat or your database.
streamingbooleanfalseTrue only for the message that is streaming now.
toolsRecord<string, AiToolConfig>title, icon, summary(input), approval { title, description, actionLabel, risk, params } and render per tool name.
onApprovalResponse({ id, approved, reason? }) => voidPass addToolApprovalResponse. Approval buttons are disabled without it.
renderApproval(props: AiApprovalRenderProps) => ReactNodeReplaces the approval UI. Receives the part, its config, pending and respond(approved).
renderPart(part, index) => ReactNode | undefinedRuns first for every part. Return undefined to use the default.
agentNamestring"Assistant"Who asks on approval requests.

getToolCallStatus

PropTypeDefaultDescription
partToolUIPart | DynamicToolUIPartMaps an AI SDK tool state to a ToolCall status and label, for custom tool UIs.

Installation

The Prodular UI CLI adds the source to components/ui with everything it depends on. In a project that already uses shadcn/ui, the shadcn CLI installs it from the Prodular registry too. See Installation for the first setup.

npx prodular-ui add ai-message-parts# or, with the registry in components.jsonnpx shadcn@latest add @prodular/ai-message-parts