Custom Chat Components

Override the composer, assistant messages, and user messages.

You can customize specific UI surfaces without rebuilding the full chat stack:

  • composer
  • assistantMessage
  • userMessage

These props replace the built-in UI entirely for that surface. If you override them, your component becomes responsible for rendering the message or composer state correctly.

Use these props when you want to swap a specific surface while keeping the built-in layout and state model. If you need to redesign the whole chat shell, use the headless APIs instead.

Custom composer

function MyComposer({ onSend, onCancel, isRunning }) {
  // your UI
}

<Copilot apiUrl="/api/chat" composer={MyComposer} />;

ComposerProps

type ComposerProps = {
  onSend: (message: string) => void;
  onCancel: () => void;
  isRunning: boolean;
  isLoadingMessages: boolean;
};

Call onSend(text) when the user submits. Use onCancel() to stop a running response.

Even a simple custom composer should still account for both isRunning and isLoadingMessages, because the composer may need to disable input while streaming or while history is still loading.

Custom assistant and user messages

function AssistantBubble({ message }) {
  return <div>{message.content}</div>;
}

function UserBubble({ message }) {
  return <div>{String(message.content)}</div>;
}

<Copilot apiUrl="/api/chat" assistantMessage={AssistantBubble} userMessage={UserBubble} />;

The message prop is the full AssistantMessage or UserMessage object from @openuidev/react-headless.

Important behavior notes

  • assistantMessage replaces the default assistant wrapper, including the avatar/container UI.
  • userMessage replaces the default user bubble wrapper.
  • If you pass componentLibrary and also pass assistantMessage, your custom component takes priority. That means you are responsible for rendering any structured assistant content yourself.
  • composer should handle both isRunning and isLoadingMessages so the input behaves correctly while streaming or loading history.
  • If your custom assistant renderer only handles plain text, document that constraint in your app and avoid assuming message.content is always a simple string.

On this page