Slots (Subcomponents)
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 you can override individually. The slot system gives you three levels of customization without needing to rebuild the entire UI:
- Tailwind classes — pass a string to add/override CSS classes
- Props override — pass an object to override specific props on the default component
- 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.
What it looks like in code#
The chat-slots cell above overrides three slots on a single <CopilotChat> —
the welcome screen, the assistant message card, and the input's disclaimer.
Each slot is just a prop; the demo extracts them into locals so the override
points are easy to see.
Welcome screen slot#
The welcomeScreen prop replaces the empty-state view shown before the first
message is sent. The demo swaps in a gradient card that still renders the
default input and suggestions:
import type { CopilotChatAssistantMessage, CopilotChatInput, CopilotChatView,} from "@copilotkit/react-core/v2";declare const CustomWelcomeScreen: React.ComponentType;declare const CustomAssistantMessage: React.ComponentType;declare const CustomDisclaimer: React.ComponentType;export function ChatSlotsTeachingExtracts() { const welcomeScreen = CustomWelcomeScreen as unknown as typeof CopilotChatView.WelcomeScreen;Assistant message slot#
Drill into messageView={{ assistantMessage: ... }} to wrap every assistant
response. The cell wraps the default component with a tinted card and a small
"slot" badge so you can see the override is active during the message flow:
import type { CopilotChatAssistantMessage, CopilotChatInput, CopilotChatView,} from "@copilotkit/react-core/v2";declare const CustomWelcomeScreen: React.ComponentType;declare const CustomAssistantMessage: React.ComponentType;declare const CustomDisclaimer: React.ComponentType;export function ChatSlotsTeachingExtracts() { const welcomeScreen = CustomWelcomeScreen as unknown as typeof CopilotChatView.WelcomeScreen; const messageView = { assistantMessage: CustomAssistantMessage as unknown as typeof CopilotChatAssistantMessage, };Disclaimer slot#
The input={{ disclaimer: ... }} sub-slot lets you replace the small text
shown below the input. The demo uses it to display a visibly tagged disclaimer
so reviewers can tell the override is still in effect once the welcome screen
is gone:
import type { CopilotChatAssistantMessage, CopilotChatInput, CopilotChatView,} from "@copilotkit/react-core/v2";declare const CustomWelcomeScreen: React.ComponentType;declare const CustomAssistantMessage: React.ComponentType;declare const CustomDisclaimer: React.ComponentType;export function ChatSlotsTeachingExtracts() { const welcomeScreen = CustomWelcomeScreen as unknown as typeof CopilotChatView.WelcomeScreen; const messageView = { assistantMessage: CustomAssistantMessage as unknown as typeof CopilotChatAssistantMessage, }; const input = { disclaimer: CustomDisclaimer as unknown as typeof CopilotChatInput.Disclaimer, };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.
import { CopilotChat } from "@copilotkit/react-core/v2";
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.
<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.
import { CopilotChat } from "@copilotkit/react-core/v2";
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:
<CopilotChat
messageView={{
assistantMessage: {
toolbar: CustomToolbar,
copyButton: CustomCopyButton,
},
userMessage: CustomUserMessage,
}}
/>Three levels deep#
Override a specific button inside the assistant message toolbar:
<CopilotChat
messageView={{
assistantMessage: {
copyButton: ({ onClick }) => (
<button onClick={onClick}>Copy</button>
),
},
}}
/>Labels#
Customize any text string in the UI via the labels prop. This is a separate convenience prop on CopilotChat, CopilotSidebar, and CopilotPopup, not part of the slot system.
<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:
| Slot | Description |
|---|---|
messageView | The message list container. |
scrollView | The scroll container with auto-scroll behavior. |
input | The text input area with send/transcribe controls. |
suggestionView | The suggestion pills shown below messages. |
welcomeScreen | The initial empty-state screen (pass false to disable). |
CopilotSidebar and CopilotPopup also have:
| Slot | Description |
|---|---|
header | The modal header bar. |
toggleButton | The open/close toggle button. |
