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 React from "react";
import {
CopilotKitProvider,
CopilotChat,
CopilotChatAssistantMessage,
useConfigureSuggestions,
} from "@copilotkit/react-core/v2";
import { CustomWelcomeScreen } from "./custom-welcome-screen";
import { CustomAssistantMessage } from "./custom-assistant-message";
import { CustomDisclaimer } from "./custom-disclaimer";
// Outer layer — provider + layout chrome.
export default function ChatSlotsDemo() {
return (
<CopilotKitProvider runtimeUrl="/api/copilotkit" useSingleEndpoint>
<div className="flex justify-center items-center h-screen w-full">
<div className="h-full w-full max-w-4xl">
<Chat />
</div>
</div>
</CopilotKitProvider>
);
}
// The actual view — just the chat, with two slot overrides.
function Chat() {
useConfigureSuggestions({
suggestions: [
{ title: "Write a sonnet", message: "Write a short sonnet about AI." },
{ title: "Tell me a joke", message: "Tell me a short joke." },
],
available: "always",
});
// Each slot is wired in as a prop on <CopilotChat>. Extracting the
// overrides up here keeps the JSX readable and gives the docs something
// to point at with `@region` markers for the slot system guide.
const welcomeScreen = CustomWelcomeScreen;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 React from "react";
import {
CopilotKitProvider,
CopilotChat,
CopilotChatAssistantMessage,
useConfigureSuggestions,
} from "@copilotkit/react-core/v2";
import { CustomWelcomeScreen } from "./custom-welcome-screen";
import { CustomAssistantMessage } from "./custom-assistant-message";
import { CustomDisclaimer } from "./custom-disclaimer";
// Outer layer — provider + layout chrome.
export default function ChatSlotsDemo() {
return (
<CopilotKitProvider runtimeUrl="/api/copilotkit" useSingleEndpoint>
<div className="flex justify-center items-center h-screen w-full">
<div className="h-full w-full max-w-4xl">
<Chat />
</div>
</div>
</CopilotKitProvider>
);
}
// The actual view — just the chat, with two slot overrides.
function Chat() {
useConfigureSuggestions({
suggestions: [
{ title: "Write a sonnet", message: "Write a short sonnet about AI." },
{ title: "Tell me a joke", message: "Tell me a short joke." },
],
available: "always",
});
// Each slot is wired in as a prop on <CopilotChat>. Extracting the
// overrides up here keeps the JSX readable and gives the docs something
// to point at with `@region` markers for the slot system guide.
const welcomeScreen = CustomWelcomeScreen;
const input = { disclaimer: CustomDisclaimer };
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 React from "react";
import {
CopilotKitProvider,
CopilotChat,
CopilotChatAssistantMessage,
useConfigureSuggestions,
} from "@copilotkit/react-core/v2";
import { CustomWelcomeScreen } from "./custom-welcome-screen";
import { CustomAssistantMessage } from "./custom-assistant-message";
import { CustomDisclaimer } from "./custom-disclaimer";
// Outer layer — provider + layout chrome.
export default function ChatSlotsDemo() {
return (
<CopilotKitProvider runtimeUrl="/api/copilotkit" useSingleEndpoint>
<div className="flex justify-center items-center h-screen w-full">
<div className="h-full w-full max-w-4xl">
<Chat />
</div>
</div>
</CopilotKitProvider>
);
}
// The actual view — just the chat, with two slot overrides.
function Chat() {
useConfigureSuggestions({
suggestions: [
{ title: "Write a sonnet", message: "Write a short sonnet about AI." },
{ title: "Tell me a joke", message: "Tell me a short joke." },
],
available: "always",
});
// Each slot is wired in as a prop on <CopilotChat>. Extracting the
// overrides up here keeps the JSX readable and gives the docs something
// to point at with `@region` markers for the slot system guide.
const welcomeScreen = CustomWelcomeScreen;
const input = { disclaimer: CustomDisclaimer };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. |
