CopilotKit

Slots

Customize any part of the chat UI by overriding individual sub-components via slots.


What is this?#

Every CopilotKit chat component is built from composable slots — named sub-components that you can override individually. The slot system gives you three levels of customization without needing to rebuild the entire UI:

  1. Tailwind classes — pass a string to add/override CSS classes
  2. Props override — pass an object to override specific props on the default component
  3. Custom component — pass your own React component to fully replace a slot

Slots are recursive — you can drill into nested sub-components at any depth.

Tailwind Classes#

The simplest way to customize a slot. Pass a Tailwind class string and it will be merged with the default component's classes.

page.tsx
export function Chat() {
  return (
    <CopilotChat
      messageView="bg-gray-50 dark:bg-gray-900 p-4"
      input="border-2 border-blue-400 rounded-xl"
    />
  );
}

Props Override#

Pass an object to override specific props on the default component. This is useful for adding className, event handlers, data attributes, or any other prop the default component accepts.

page.tsx
<CopilotChat
  messageView={{
    className: "my-custom-messages",
    "data-testid": "message-view",
  }}
  input={{ autoFocus: true }}
/>

Custom Components#

For full control, pass your own React component. It receives all the same props as the default component.

page.tsx
const CustomMessageView = ({ messages, isRunning }) => (
  <div className="space-y-4 p-6">
    {messages?.map((msg) => (
      <div
        key={msg.id}
        className={msg.role === "user" ? "text-right" : "text-left"}
      >
        {msg.content}
      </div>
    ))}
    {isRunning && <div className="animate-pulse">Thinking...</div>}
  </div>
);

export function Chat() {
  return (
    <CopilotChat messageView={CustomMessageView} />
  );
}

Nested Slots (Drill-Down)#

Slots are recursive. You can customize sub-components at any depth by nesting objects.

Two levels deep#

Override the assistant message's toolbar within the message view:

page.tsx
<CopilotChat
  messageView={{
    assistantMessage: {
      toolbar: CustomToolbar,
      copyButton: CustomCopyButton,
    },
    userMessage: CustomUserMessage,
  }}
/>

Three levels deep#

Override a specific button inside the assistant message toolbar:

page.tsx
<CopilotChat
  messageView={{
    assistantMessage: {
      copyButton: ({ onClick }) => <button onClick={onClick}>Copy</button>,
    },
  }}
/>

Input sub-slots#

page.tsx
<CopilotChat
  input={{
    textArea: CustomTextArea,
    sendButton: CustomSendButton,
  }}
/>

Scroll view sub-slots#

page.tsx
<CopilotChat
  scrollView={{
    feather: CustomFeather,
    scrollToBottomButton: CustomScrollButton,
  }}
/>

Suggestion view sub-slots#

page.tsx
<CopilotChat
  suggestionView={{
    suggestion: CustomSuggestionPill,
    container: CustomSuggestionContainer,
  }}
/>

Children Render Function#

For complete layout control, use the children render function pattern. This gives you pre-built slot elements that you can arrange however you want.

page.tsx
export function Chat() {
  return (
    <CopilotChat>
      {({ messageView, input, scrollView, suggestionView }) => (
        <div className="flex flex-col h-full">
          <header className="p-4 border-b font-semibold">My Agent</header>
          {scrollView}
          <div className="border-t p-4">{input}</div>
        </div>
      )}
    </CopilotChat>
  );
}

Labels#

Customize any text string in the UI via the labels prop. This does not use the slot system — it's a separate convenience prop on CopilotChat, CopilotSidebar, and CopilotPopup.

page.tsx
<CopilotChat
  labels={{
    chatInputPlaceholder: "Ask your agent anything...",
    welcomeMessageText: "How can I help you today?",
    chatDisclaimerText: "AI responses may be inaccurate.",
  }}
/>

Available Slots#

CopilotChat / CopilotSidebar / CopilotPopup#

These are the root-level slot props available on all chat components:

SlotDescription
messageViewThe message list container.
scrollViewThe scroll container with auto-scroll behavior.
inputThe text input area with send/transcribe controls.
suggestionViewThe suggestion pills shown below messages.
welcomeScreenThe initial empty-state screen (pass false to disable).

CopilotSidebar and CopilotPopup also have:

SlotDescription
headerThe modal header bar.
toggleButtonThe open/close toggle button.

messageView sub-slots#

Available via messageView={{ ... }}:

SlotDescription
assistantMessageRenders assistant responses. Has its own sub-slots (see below).
userMessageRenders user messages. Has its own sub-slots (see below).
reasoningMessageRenders model reasoning/thinking steps. Has its own sub-slots (see below).
cursorThe streaming cursor indicator shown while the agent is responding.

assistantMessage sub-slots#

Available via messageView={{ assistantMessage: { ... } }}:

SlotDescription
markdownRendererThe markdown rendering component.
toolbarThe action toolbar below messages.
copyButtonCopy message button.
thumbsUpButtonThumbs up feedback button.
thumbsDownButtonThumbs down feedback button.
readAloudButtonRead aloud button.
regenerateButtonRegenerate response button.
toolCallsViewTool call visualization.

userMessage sub-slots#

Available via messageView={{ userMessage: { ... } }}:

SlotDescription
messageRendererThe text rendering component for user messages.
toolbarThe action toolbar on hover.
copyButtonCopy message button.
editButtonEdit message button.
branchNavigationNavigation between message branches (after editing).

reasoningMessage sub-slots#

Available via messageView={{ reasoningMessage: { ... } }}:

SlotDescription
headerThe collapsible header (click to expand/collapse).
contentViewThe reasoning content area.
toggleThe expand/collapse toggle wrapper.

input sub-slots#

Available via input={{ ... }}:

SlotDescription
textAreaThe text input element.
sendButtonThe send/submit button.
addMenuButtonThe attachment/tools menu button.
startTranscribeButtonButton to start voice transcription.
cancelTranscribeButtonButton to cancel transcription.
finishTranscribeButtonButton to finish transcription.
audioRecorderThe audio recorder component.
disclaimerThe disclaimer text below the input.

scrollView sub-slots#

Available via scrollView={{ ... }}:

SlotDescription
featherThe gradient overlay at the bottom of the scroll area.
scrollToBottomButtonThe button that appears when scrolled up.

suggestionView sub-slots#

Available via suggestionView={{ ... }}:

SlotDescription
suggestionIndividual suggestion pill/button.
containerThe container wrapping all suggestion pills.

welcomeScreen sub-slots#

Available via welcomeScreen={{ ... }}:

SlotDescription
welcomeMessageThe welcome text shown on the empty state.

header sub-slots (Sidebar/Popup only)#

Available via header={{ ... }}:

SlotDescription
titleContentThe title text in the header.
closeButtonThe close/minimize button.

toggleButton sub-slots (Sidebar/Popup only)#

Available via toggleButton={{ ... }}:

SlotDescription
openIconIcon shown when the chat is closed.
closeIconIcon shown when the chat is open.