Migration to v0.14

Primitives migrate from components prop to children render functions.

Children API for Primitives

Version 0.14 replaces the components prop on primitives with a children render function pattern. This gives you full control over rendering with simple inline logic.

ThreadPrimitive.Messages

Before:

<ThreadPrimitive.Messages
  components={{
    UserMessage,
    EditComposer,
    AssistantMessage,
  }}
/>

After:

<ThreadPrimitive.Messages>
  {({ message }) => {
    if (message.composer.isEditing) return <EditComposer />;
    if (message.role === "user") return <UserMessage />;
    return <AssistantMessage />;
  }}
</ThreadPrimitive.Messages>

MessagePrimitive.Parts

Before:

<MessagePrimitive.Parts
  components={{
    Text: MarkdownText,
    Reasoning,
    tools: { Fallback: ToolFallback },
  }}
/>

After:

<MessagePrimitive.Parts>
  {({ part }) => {
    if (part.type === "text") return <MarkdownText />;
    if (part.type === "reasoning") return <Reasoning {...part} />;
    if (part.type === "tool-call")
      return part.toolUI ?? <ToolFallback {...part} />;
    return null;
  }}
</MessagePrimitive.Parts>

part.toolUI and part.dataRendererUI

Tool-call parts now expose a toolUI property that resolves to the registered tool UI (via useAssistantToolUI / makeAssistantToolUI) or null if none is registered. Data parts similarly expose dataRendererUI.

// Renders the registered tool UI if available, otherwise ToolFallback
if (part.type === "tool-call")
  return part.toolUI ?? <ToolFallback {...part} />;

// Renders the registered data renderer if available
if (part.type === "data")
  return part.dataRendererUI ?? null;

Returning null

Returning null from the children function renders registered tool UIs and data renderer UIs automatically via the registry. To explicitly render nothing (suppressing registered UIs), return <></>.

<MessagePrimitive.Parts>
  {({ part }) => {
    if (part.type === "text") return <MarkdownText />;
    return null; // registered tool/data UIs still render
  }}
</MessagePrimitive.Parts>

ThreadPrimitive.Suggestions

Before:

<ThreadPrimitive.Suggestions
  components={{ Suggestion: SuggestionItem }}
/>

After:

<ThreadPrimitive.Suggestions>
  {() => <SuggestionItem />}
</ThreadPrimitive.Suggestions>

ThreadListPrimitive.Items

Before:

<ThreadListPrimitive.Items
  components={{ ThreadListItem: MyThreadListItem }}
/>

After:

<ThreadListPrimitive.Items>
  {() => <MyThreadListItem />}
</ThreadListPrimitive.Items>

Attachments

Before:

<ComposerPrimitive.Attachments
  components={{ Attachment: MyAttachment }}
/>

After:

<ComposerPrimitive.Attachments>
  {() => <MyAttachment />}
</ComposerPrimitive.Attachments>

addResult / resume on enriched part state

When using the children API, tool-call parts include addResult and resume methods directly on the part object. This means <ToolFallback {...part} /> works without needing a wrapper component to provide these methods.

Backwards Compatibility

The components prop is still supported on all primitives but is deprecated and will be removed in a future version.

Getting Help

If you encounter issues during migration:

  1. Check the updated API documentation for detailed examples
  2. Review the example applications in the repository
  3. Report issues at https://github.com/assistant-ui/assistant-ui/issues