--- title: Slot System description: "Deep customization system for CopilotKit components" --- The Slot System is CopilotKit's approach to component customization. It allows you to customize any part of the UI - from simple styling changes to complete component replacement - all through a consistent, composable API. ## What is the Slot System? The Slot System: - Provides four levels of customization depth - Maintains type safety throughout the customization process - Supports nested slots for drilling into child components - Uses automatic memoization for optimal performance - Works consistently across all CopilotKit components ## Four Customization Levels Every slot accepts one of four value types, from simplest to most flexible: ### 1. Tailwind Class String Pass a string of Tailwind classes to add or override styles: ```tsx ``` Classes are merged with the component's existing classes using `tailwind-merge`, so conflicting classes are resolved intelligently. ### 2. Props Object Pass an object of props to customize behavior while keeping the default component: ```tsx trackFeedback(msg.id, "positive"), }, }} /> ``` Props are merged with defaults, and you can include nested slots to drill down to child components. ### 3. Custom Component Replace the component entirely with your own implementation: ```tsx function CustomInput({ onSubmitMessage, isRunning, ...props }) { return (
); } ; ``` Custom components receive all the props that would have been passed to the default component. ### 4. Render Function (Children) For full layout control, use the children render function pattern: ```tsx function CustomInput(props) { return ( {({ textArea, sendButton, addMenuButton }) => (
{addMenuButton}
{textArea}
{sendButton}
)}
); } ; ``` The render function receives pre-built slot elements that you can arrange however you like. ## Nested Slot Customization Slots can be nested to customize deeply nested components: ```tsx null, // Hide the button }, userMessage: "bg-blue-100 rounded-xl", }} input={{ textArea: "text-lg", sendButton: "bg-green-500", }} /> ``` ## Hiding Components To hide a slot entirely, return `null` from a component function: ```tsx null, // Hide disclaimer startTranscribeButton: () => null, // Hide voice button }} messageView={{ assistantMessage: { regenerateButton: () => null, // Hide regenerate }, }} /> ``` ## Complete Slot Hierarchy Here's the full hierarchy of all customizable slots in CopilotChat: ``` CopilotChat ├── chatView │ ├── messageView │ │ ├── assistantMessage │ │ │ ├── markdownRenderer │ │ │ ├── toolbar │ │ │ ├── copyButton │ │ │ ├── thumbsUpButton │ │ │ ├── thumbsDownButton │ │ │ ├── readAloudButton │ │ │ ├── regenerateButton │ │ │ └── toolCallsView │ │ ├── userMessage (see CopilotChatUserMessage) │ │ │ ├── messageRenderer │ │ │ ├── toolbar │ │ │ ├── copyButton │ │ │ ├── editButton │ │ │ └── branchNavigation │ │ └── cursor │ ├── scrollView │ │ ├── scrollToBottomButton │ │ └── feather │ ├── input │ │ ├── textArea │ │ ├── sendButton │ │ ├── startTranscribeButton │ │ ├── cancelTranscribeButton │ │ ├── finishTranscribeButton │ │ ├── addMenuButton │ │ ├── audioRecorder │ │ └── disclaimer │ ├── suggestionView │ │ ├── container │ │ └── suggestion │ └── welcomeScreen │ └── welcomeMessage ``` ## How It Works Under the hood, the slot system uses three key concepts: ### SlotValue Type Every slot accepts one of three value types: ```typescript type SlotValue> = | C // Custom component | string // Tailwind class string | Partial>; // Props object ``` ### renderSlot Function The `renderSlot` function resolves a slot value into a React element: ```typescript // Internal implementation (simplified) function renderSlot(slot, DefaultComponent, props) { if (typeof slot === "string") { // Merge className with existing return ; } if (isReactComponent(slot)) { // Use custom component return ; } if (isPropsObject(slot)) { // Merge props return ; } // Use default return ; } ``` ### WithSlots Type Components use the `WithSlots` type to define their slot interface: ```typescript type MyComponentProps = WithSlots< { button: typeof MyButton; input: typeof MyInput; }, { value: string; onChange: (value: string) => void; } >; ``` ## Best Practices ### 1. Start Simple, Escalate as Needed Begin with Tailwind classes, then move to props objects, and only use custom components when necessary: ```tsx // Start here // Then this // Only if needed ``` ### 2. Use Props Objects for Nested Customization When customizing nested slots, use props objects to drill down: ```tsx ``` ### 3. Preserve Default Behavior When creating custom components, spread the remaining props to preserve default functionality: ```tsx function CustomButton({ onClick, disabled, className, ...props }) { return (