Production-ready React UI components library for building deeply-integrated AI assistants and copilots
Comprehensive customization system for tailoring the appearance and behavior of CopilotKit chat interfaces. Supports multiple levels of customization from simple (labels/icons) to advanced (complete component replacement).
Customize all icons used throughout the chat interface.
interface CopilotChatIcons {
/** Icon for opening the chat (popup/sidebar button) */
openIcon?: React.ReactNode;
/** Icon for closing the chat (popup/sidebar button) */
closeIcon?: React.ReactNode;
/** Icon for header close button */
headerCloseIcon?: React.ReactNode;
/** Icon for send message button */
sendIcon?: React.ReactNode;
/** Icon for activity/loading indicator */
activityIcon?: React.ReactNode;
/** Spinner icon for loading states */
spinnerIcon?: React.ReactNode;
/** Icon for stop generation button */
stopIcon?: React.ReactNode;
/** Icon for regenerate response button */
regenerateIcon?: React.ReactNode;
/** Icon for push-to-talk microphone button */
pushToTalkIcon?: React.ReactNode;
/** Icon for copy to clipboard button */
copyIcon?: React.ReactNode;
/** Icon for positive feedback button */
thumbsUpIcon?: React.ReactNode;
/** Icon for negative feedback button */
thumbsDownIcon?: React.ReactNode;
/** Icon for upload/attach file button */
uploadIcon?: React.ReactNode;
}Usage Example:
import { CopilotPopup } from "@copilotkit/react-ui";
import {
SendIcon,
ThumbsUpIcon,
ThumbsDownIcon,
CopyIcon,
} from "./my-icons";
function App() {
return (
<CopilotPopup
instructions="You are helpful."
icons={{
sendIcon: <SendIcon className="w-5 h-5" />,
copyIcon: <CopyIcon />,
thumbsUpIcon: <ThumbsUpIcon />,
thumbsDownIcon: <ThumbsDownIcon />,
regenerateIcon: "🔄",
stopIcon: "⏹️",
}}
/>
);
}Customize all text labels displayed in the chat interface.
interface CopilotChatLabels {
/** Initial message(s) displayed when chat starts - string or array of strings */
initial?: string | string[];
/** Chat header title (default: "CopilotKit") */
title?: string;
/** Input field placeholder (default: "Type a message...") */
placeholder?: string;
/** Error message text (default: "❌ An error occurred. Please try again.") */
error?: string;
/** Stop button label (default: "Stop generating") */
stopGenerating?: string;
/** Regenerate button label (default: "Regenerate response") */
regenerateResponse?: string;
/** Copy button label (default: "Copy to clipboard") */
copyToClipboard?: string;
/** Thumbs up button label (default: "Thumbs up") */
thumbsUp?: string;
/** Thumbs down button label (default: "Thumbs down") */
thumbsDown?: string;
/** Copied confirmation text (default: "Copied!") */
copied?: string;
}Usage Example:
import { CopilotChat } from "@copilotkit/react-ui";
function App() {
return (
<CopilotChat
instructions="You are a travel assistant."
labels={{
title: "Travel Buddy",
initial: [
"Welcome to Travel Buddy! 🌍",
"I can help you plan your next adventure.",
"Try asking about destinations, booking flights, or travel tips!",
],
placeholder: "Ask about travel plans...",
error: "Oops! Something went wrong. Please try again.",
stopGenerating: "Stop",
regenerateResponse: "Try again",
copyToClipboard: "Copy",
thumbsUp: "Helpful",
thumbsDown: "Not helpful",
copied: "Copied to clipboard!",
}}
/>
);
}Type-safe CSS custom properties for comprehensive visual theming. Apply to any parent element to theme all nested CopilotKit components.
interface CopilotKitCSSProperties extends React.CSSProperties {
/** Primary brand color */
"--copilot-kit-primary-color"?: string;
/** Contrast color for primary (text on primary background) */
"--copilot-kit-contrast-color"?: string;
/** Background color for chat panel */
"--copilot-kit-background-color"?: string;
/** Background color for input field */
"--copilot-kit-input-background-color"?: string;
/** Secondary UI color */
"--copilot-kit-secondary-color"?: string;
/** Contrast color for secondary (text on secondary background) */
"--copilot-kit-secondary-contrast-color"?: string;
/** Color for separators and borders */
"--copilot-kit-separator-color"?: string;
/** Color for muted/secondary text */
"--copilot-kit-muted-color"?: string;
/** Error background color */
"--copilot-kit-error-background"?: string;
/** Error border color */
"--copilot-kit-error-border"?: string;
/** Error text color */
"--copilot-kit-error-text"?: string;
/** Small shadow for subtle elevation */
"--copilot-kit-shadow-sm"?: string;
/** Medium shadow for moderate elevation */
"--copilot-kit-shadow-md"?: string;
/** Large shadow for prominent elevation */
"--copilot-kit-shadow-lg"?: string;
/** Dev console background color */
"--copilot-kit-dev-console-bg"?: string;
/** Dev console text color */
"--copilot-kit-dev-console-text"?: string;
}Usage Example:
import { CopilotPopup, CopilotKitCSSProperties } from "@copilotkit/react-ui";
function App() {
const customStyles: CopilotKitCSSProperties = {
"--copilot-kit-primary-color": "#6366f1",
"--copilot-kit-contrast-color": "#ffffff",
"--copilot-kit-background-color": "#f9fafb",
"--copilot-kit-input-background-color": "#ffffff",
"--copilot-kit-secondary-color": "#e5e7eb",
"--copilot-kit-secondary-contrast-color": "#1f2937",
"--copilot-kit-separator-color": "#d1d5db",
"--copilot-kit-muted-color": "#6b7280",
"--copilot-kit-shadow-md": "0 4px 6px -1px rgb(0 0 0 / 0.1)",
};
return (
<div style={customStyles}>
<CopilotPopup instructions="You are helpful." />
</div>
);
}You can also apply CSS variables in a stylesheet:
.my-app {
--copilot-kit-primary-color: #6366f1;
--copilot-kit-contrast-color: #ffffff;
--copilot-kit-background-color: #f9fafb;
--copilot-kit-input-background-color: #ffffff;
--copilot-kit-secondary-color: #e5e7eb;
--copilot-kit-separator-color: #d1d5db;
--copilot-kit-muted-color: #6b7280;
--copilot-kit-shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--copilot-kit-shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
--copilot-kit-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
}
/* Dark mode theme */
.dark-mode {
--copilot-kit-primary-color: #818cf8;
--copilot-kit-contrast-color: #1e1b4b;
--copilot-kit-background-color: #1f2937;
--copilot-kit-input-background-color: #374151;
--copilot-kit-secondary-color: #4b5563;
--copilot-kit-secondary-contrast-color: #f9fafb;
--copilot-kit-separator-color: #6b7280;
--copilot-kit-muted-color: #9ca3af;
}Customize how markdown elements are rendered in assistant messages.
type ComponentsMap<T extends Record<string, object> = Record<string, object>> = {
[K in keyof T]: React.FC<{ children?: React.ReactNode } & T[K]>;
};Usage Example:
import { CopilotChat } from "@copilotkit/react-ui";
import { Components } from "react-markdown";
function App() {
const customRenderers: Partial<Components> = {
// Custom heading renderer
h1: ({ children }) => (
<h1 className="text-3xl font-bold text-purple-600 mb-4">
{children}
</h1>
),
// Custom link renderer with external icon
a: ({ href, children }) => (
<a
href={href}
className="text-blue-600 hover:underline"
target="_blank"
rel="noopener noreferrer"
>
{children} 🔗
</a>
),
// Custom code block with syntax highlighting wrapper
code: ({ inline, className, children }) => {
if (inline) {
return (
<code className="bg-gray-100 px-1 py-0.5 rounded text-sm">
{children}
</code>
);
}
return (
<pre className="bg-gray-900 text-white p-4 rounded-lg overflow-x-auto">
<code className={className}>{children}</code>
</pre>
);
},
// Custom list renderer
ul: ({ children }) => (
<ul className="list-disc list-inside space-y-2 ml-4">
{children}
</ul>
),
// Custom blockquote
blockquote: ({ children }) => (
<blockquote className="border-l-4 border-blue-500 pl-4 italic text-gray-700">
{children}
</blockquote>
),
};
return (
<CopilotChat
instructions="You are helpful."
markdownTagRenderers={customRenderers}
/>
);
}Replace default components with custom implementations for complete control over rendering.
Available Component Props:
interface CopilotChatProps {
/** Custom assistant message component */
AssistantMessage?: React.ComponentType<AssistantMessageProps>;
/** Custom user message component */
UserMessage?: React.ComponentType<UserMessageProps>;
/** Custom error message component */
ErrorMessage?: React.ComponentType<ErrorMessageProps>;
/** Custom messages container component */
Messages?: React.ComponentType<MessagesProps>;
/** Custom message renderer */
RenderMessage?: React.ComponentType<RenderMessageProps>;
/** Custom suggestions list renderer */
RenderSuggestionsList?: React.ComponentType<RenderSuggestionsListProps>;
/** Custom input component */
Input?: React.ComponentType<InputProps>;
/** Custom image renderer */
ImageRenderer?: React.ComponentType<ImageRendererProps>;
}
interface CopilotModalProps extends CopilotChatProps {
/** Custom window/modal component */
Window?: React.ComponentType<WindowProps>;
/** Custom toggle button component */
Button?: React.ComponentType<ButtonProps>;
/** Custom header component */
Header?: React.ComponentType<HeaderProps>;
}Usage Example - Custom Assistant Message:
import { CopilotChat, AssistantMessageProps, Markdown } from "@copilotkit/react-ui";
function CustomAssistantMessage({
message,
isLoading,
isGenerating,
onRegenerate,
onCopy,
}: AssistantMessageProps) {
return (
<div className="assistant-message">
<div className="avatar">
<img src="/bot-avatar.png" alt="AI" />
</div>
<div className="content">
{isLoading && <div className="thinking">Thinking...</div>}
{message && <Markdown content={message.content} />}
{isGenerating && <span className="cursor">▊</span>}
</div>
<div className="actions">
{onRegenerate && (
<button onClick={onRegenerate}>Regenerate</button>
)}
{onCopy && message && (
<button onClick={() => onCopy(message.content)}>Copy</button>
)}
</div>
</div>
);
}
function App() {
return (
<CopilotChat
instructions="You are helpful."
AssistantMessage={CustomAssistantMessage}
/>
);
}Usage Example - Custom Input Component:
import { CopilotChat, InputProps } from "@copilotkit/react-ui";
import { useState } from "react";
function CustomInput({ inProgress, onSend, onStop }: InputProps) {
const [text, setText] = useState("");
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!text.trim() || inProgress) return;
await onSend(text);
setText("");
};
return (
<form onSubmit={handleSubmit} className="custom-input">
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Type your message..."
disabled={inProgress}
className="input-field"
/>
{inProgress ? (
<button type="button" onClick={onStop} className="stop-btn">
Stop
</button>
) : (
<button type="submit" disabled={!text.trim()} className="send-btn">
Send
</button>
)}
</form>
);
}
function App() {
return (
<CopilotChat
instructions="You are helpful."
Input={CustomInput}
/>
);
}Customize or disable the system message sent to the AI.
type SystemMessageFunction = (args: {
instructions?: string;
// Additional context
}) => string;Usage Example:
import { CopilotChat } from "@copilotkit/react-ui";
function App() {
const customSystemMessage = ({ instructions }) => {
return `
You are an expert assistant with the following guidelines:
${instructions || "Be helpful and concise."}
Important rules:
- Always format code with proper syntax highlighting
- Provide step-by-step explanations for complex topics
- Ask clarifying questions when needed
- Current date: ${new Date().toLocaleDateString()}
`;
};
return (
<CopilotChat
instructions="You are a coding tutor."
makeSystemMessage={customSystemMessage}
/>
);
}
// Or disable system message entirely
function AppNoSystem() {
return (
<CopilotChat
disableSystemMessage={true}
/>
);
}Configure image upload functionality.
interface CopilotChatProps {
/** Enable image upload functionality */
imageUploadsEnabled?: boolean;
/** File input accept attribute (default: "image/*") */
inputFileAccept?: string;
}Usage Example:
import { CopilotChat } from "@copilotkit/react-ui";
function App() {
return (
<CopilotChat
instructions="You are a helpful assistant."
imageUploadsEnabled={true}
inputFileAccept="image/png,image/jpeg,image/gif"
/>
);
}Customize error rendering and handling.
interface CopilotChatProps {
/** Custom error renderer for inline chat errors */
renderError?: (error: {
/** Error message text */
message: string;
/** Operation that caused the error */
operation?: string;
/** Error timestamp */
timestamp: number;
/** Callback to dismiss the error */
onDismiss: () => void;
/** Optional callback to retry the failed operation */
onRetry?: () => void;
}) => React.ReactNode;
/** Error handler for debugging and observability */
onError?: CopilotErrorHandler;
}
type CopilotErrorHandler = (error: Error) => void;Usage Example:
import { CopilotChat } from "@copilotkit/react-ui";
function App() {
return (
<CopilotChat
instructions="You are helpful."
renderError={({ message, operation, onDismiss, onRetry }) => (
<div className="custom-error-banner">
<div className="error-content">
<h3>⚠️ Error</h3>
<p>{message}</p>
{operation && <small>During: {operation}</small>}
</div>
<div className="error-actions">
{onRetry && (
<button onClick={onRetry} className="retry-btn">
Retry
</button>
)}
<button onClick={onDismiss} className="dismiss-btn">
Dismiss
</button>
</div>
</div>
)}
onError={(error) => {
console.error("Chat error:", error);
// Send to monitoring service
}}
/>
);
}Alternative Example - Simple Banner:
function App() {
return (
<CopilotChat
renderError={({ message, onDismiss }) => (
<div className="simple-error">
<span>{message}</span>
<button onClick={onDismiss}>×</button>
</div>
)}
/>
);
}Replace default UI components with custom implementations. Empty interfaces for Button, Header, and Window are intentionally extensible to allow custom props.
interface CopilotModalProps {
/** Custom window/modal wrapper */
Window?: React.ComponentType<WindowProps>;
/** Custom toggle button */
Button?: React.ComponentType<ButtonProps>;
/** Custom header */
Header?: React.ComponentType<HeaderProps>;
}
interface WindowProps {
clickOutsideToClose: boolean;
hitEscapeToClose: boolean;
shortcut: string;
children?: React.ReactNode;
}
/** Extensible interface for custom button implementations */
interface ButtonProps {
// Empty - extend with your own props
}
/** Extensible interface for custom header implementations */
interface HeaderProps {
// Empty - extend with your own props
}Usage Example:
import { CopilotPopup, useChatContext } from "@copilotkit/react-ui";
function CustomButton() {
const { open, setOpen, icons } = useChatContext();
return (
<button
onClick={() => setOpen(!open)}
className="my-custom-button"
>
{open ? icons.closeIcon : icons.openIcon}
</button>
);
}
function App() {
return (
<CopilotPopup
Button={CustomButton}
instructions="You are helpful."
/>
);
}Customize how specific markdown elements are rendered within AI messages.
type ComponentsMap<T extends Record<string, object> = Record<string, object>> = {
[K in keyof T]: React.FC<{ children?: React.ReactNode } & T[K]>;
};
interface CopilotChatProps {
/** Custom renderers for markdown elements */
markdownTagRenderers?: ComponentsMap;
}Usage Example:
import { CopilotChat } from "@copilotkit/react-ui";
function App() {
return (
<CopilotChat
markdownTagRenderers={{
a: ({ href, children }) => (
<a
href={href}
target="_blank"
rel="noopener noreferrer"
className="custom-link"
>
{children} 🔗
</a>
),
code: ({ className, children }) => {
const isInline = !className;
return isInline ? (
<code className="inline-code">{children}</code>
) : (
<code className={className}>{children}</code>
);
},
reference: ({ id, children }) => (
<span className="reference-tag" data-ref-id={id}>
{children}
</span>
),
}}
/>
);
}Example - Custom Reference Tags:
function App() {
return (
<CopilotChat
instructions="Use <reference id='doc-123'>document name</reference> tags to cite sources."
markdownTagRenderers={{
reference: ({ id, children }: { id: string; children: React.ReactNode }) => (
<button
onClick={() => openDocument(id)}
className="doc-reference"
>
📄 {children}
</button>
),
}}
/>
);
}Replace entire component implementations for complete control.
interface CopilotChatProps {
/** Custom assistant message component */
AssistantMessage?: React.ComponentType<AssistantMessageProps>;
/** Custom user message component */
UserMessage?: React.ComponentType<UserMessageProps>;
/** Custom error message component */
ErrorMessage?: React.ComponentType<ErrorMessageProps>;
/** Custom messages container */
Messages?: React.ComponentType<MessagesProps>;
/** Custom input component */
Input?: React.ComponentType<InputProps>;
/** Custom message renderer (break-glass for full control) */
RenderMessage?: React.ComponentType<RenderMessageProps>;
/** Custom suggestions list */
RenderSuggestionsList?: React.ComponentType<RenderSuggestionsListProps>;
/** Custom image renderer */
ImageRenderer?: React.ComponentType<ImageRendererProps>;
}Usage Example - Custom Input:
import { CopilotChat } from "@copilotkit/react-ui";
import type { InputProps } from "@copilotkit/react-ui";
import { useState } from "react";
function CustomInput({ inProgress, onSend, onStop }: InputProps) {
const [text, setText] = useState("");
const [attachments, setAttachments] = useState<File[]>([]);
const handleSubmit = async () => {
if (!text.trim()) return;
await onSend(text);
setText("");
setAttachments([]);
};
return (
<div className="custom-input">
<div className="attachments">
{attachments.map((file, i) => (
<span key={i}>{file.name}</span>
))}
</div>
<textarea
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Type your message..."
disabled={inProgress}
/>
<div className="actions">
<input
type="file"
onChange={(e) => setAttachments([...attachments, ...e.target.files])}
/>
{inProgress ? (
<button onClick={onStop}>Stop</button>
) : (
<button onClick={handleSubmit}>Send</button>
)}
</div>
</div>
);
}
function App() {
return <CopilotChat Input={CustomInput} />;
}Combining multiple customization approaches for a fully themed interface:
import {
CopilotPopup,
CopilotKitCSSProperties,
AssistantMessageProps,
Markdown,
} from "@copilotkit/react-ui";
// Custom styled assistant message
function BrandedAssistantMessage({
message,
isLoading,
onCopy,
}: AssistantMessageProps) {
return (
<div className="branded-message">
<div className="avatar">
<img src="/brand-logo.png" alt="Assistant" />
</div>
<div className="content">
{isLoading ? (
<div className="pulse">Thinking...</div>
) : (
message && <Markdown content={message.content} />
)}
</div>
{message && onCopy && (
<button onClick={() => onCopy(message.content)}>📋</button>
)}
</div>
);
}
function App() {
const brandTheme: CopilotKitCSSProperties = {
"--copilot-kit-primary-color": "#FF6B6B",
"--copilot-kit-contrast-color": "#FFFFFF",
"--copilot-kit-background-color": "#FFF5F5",
"--copilot-kit-secondary-color": "#FFE5E5",
"--copilot-kit-separator-color": "#FFD0D0",
};
return (
<div style={brandTheme}>
<CopilotPopup
instructions="You are our branded assistant."
labels={{
title: "BrandBot",
initial: "Welcome! I'm here to help.",
placeholder: "Ask me anything...",
}}
icons={{
sendIcon: "➤",
regenerateIcon: "🔄",
copyIcon: "📋",
thumbsUpIcon: "👍",
thumbsDownIcon: "👎",
}}
AssistantMessage={BrandedAssistantMessage}
/>
</div>
);
}Install with Tessl CLI
npx tessl i tessl/npm-copilotkit--react-ui@1.10.1