ChatKit Frontend Skill
Production-ready skill for implementing OpenAI ChatKit in Next.js applications.
Official Documentation (ALWAYS verify before implementation):
-
OpenAI ChatKit Docs
-
ChatKit.js Docs
-
GitHub Repository
-
Advanced Samples
-
Domain Allowlist - Required for production
Overview
OpenAI ChatKit is a batteries-included framework for building AI-powered chat experiences with:
-
Deep UI Customization - Theme, colors, radius, typography
-
Built-in Streaming - Natural real-time conversations
-
Tool Integration - Display agent actions and reasoning
-
Interactive Widgets - Rich content in chat messages
-
File Handling - Upload and attachment support
-
Thread Management - Conversation history and switching
Architecture
┌─────────────────────────────────────────────────────────────────────────┐ │ Next.js Frontend │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────────┐ ┌──────────────────────────────────────────┐ │ │ │ ConversationSidebar │ │ ChatKit Component │ │ │ │ (Custom) │ │ ┌────────────────────────────────────┐ │ │ │ │ │ │ │ <ChatKit control={control} /> │ │ │ │ │ - Thread list │◄──►│ │ - Messages │ │ │ │ │ - New chat │ │ │ - Input │ │ │ │ │ - Delete/rename │ │ │ - Streaming │ │ │ │ └──────────────────┘ │ │ - Tool indicators │ │ │ │ │ └────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────┘ │ │ │ │ │ ┌───────────────────▼───────────────────┐ │ │ │ useChatKit Hook │ │ │ │ - api: { url, domainKey } │ │ │ │ - theme: { colorScheme, radius } │ │ │ │ - startScreen: { greeting, prompts } │ │ │ │ - onClientTool: (invocation) => {} │ │ │ └───────────────────┬───────────────────┘ │ └──────────────────────────────────────────────┼───────────────────────────┘ │ SSE Stream ┌────────────────────▼────────────────────┐ │ FastAPI Backend │ │ POST /chatkit │ │ - ChatKit-compatible SSE format │ └─────────────────────────────────────────┘
Installation
Step 1: Install ChatKit Package
cd frontend npm install @openai/chatkit-react
Step 2: Verify Installation
Check package.json
grep chatkit package.json
Quick Start
Basic ChatKit Integration
// app/chat/page.tsx 'use client';
import { ChatKit, useChatKit } from '@openai/chatkit-react';
export default function ChatPage() {
const { control } = useChatKit({
api: {
url: ${process.env.NEXT_PUBLIC_API_URL}/chatkit,
domainKey: process.env.NEXT_PUBLIC_OPENAI_DOMAIN_KEY || 'local-dev',
},
});
return ( <div className="h-screen w-full"> <ChatKit control={control} className="h-full w-full" /> </div> ); }
With Full Configuration
// app/chat/page.tsx 'use client';
import { ChatKit, useChatKit } from '@openai/chatkit-react'; import { useAuthStore } from '@/stores/auth-store'; import { useConversationStore } from '@/stores/conversation-store';
export default function ChatPage() { const { user } = useAuthStore(); const { currentConversation, refreshConversations } = useConversationStore();
const { control } = useChatKit({
// API Configuration
api: {
url: ${process.env.NEXT_PUBLIC_API_URL}/chatkit,
domainKey: process.env.NEXT_PUBLIC_OPENAI_DOMAIN_KEY || 'local-dev',
},
// Theme Configuration
theme: {
colorScheme: 'light', // 'light' | 'dark' | 'system'
radius: 'round', // 'sharp' | 'round' | 'pill'
color: {
accent: { primary: '#0066cc', level: 2 },
grayscale: { hue: 220, tint: 6, shade: -4 },
},
},
// Start Screen
startScreen: {
greeting: `Hello${user?.name ? `, ${user.name}` : ''}! How can I help you today?`,
prompts: [
'Show my tasks',
'Add a new task',
'What tasks are due today?',
'Mark my grocery task as complete',
],
},
// Header Configuration
header: {
enabled: true,
title: 'Task Assistant',
},
// Composer Configuration
composer: {
placeholder: 'Ask about your tasks...',
},
// History (built-in - optional, we use custom sidebar)
history: {
enabled: false, // Disable built-in, use custom ConversationSidebar
},
// Client-side Tool Handling
onClientTool: async (invocation) => {
console.log('Client tool invoked:', invocation.name, invocation.params);
// Handle client-side tools (theme switching, etc.)
if (invocation.name === 'switch_theme') {
// Handle theme change
return { success: true };
}
return { success: false };
},
// Error Handling
onError: ({ error }) => {
console.error('ChatKit error:', error);
},
// Message Events
onMessage: (message) => {
console.log('New message:', message);
// Refresh conversations after message
refreshConversations();
},
});
return ( <div className="h-screen w-full"> <ChatKit control={control} className="h-full w-full max-w-4xl mx-auto" /> </div> ); }
Project Structure
frontend/ ├── app/ │ └── chat/ │ ├── layout.tsx # Chat layout with sidebar │ └── page.tsx # ChatKit page │ ├── components/ │ ├── chat/ │ │ └── ChatKitWrapper.tsx # Optional ChatKit wrapper │ │ │ └── conversation/ # Custom sidebar (keep existing) │ ├── ConversationSidebar.tsx │ ├── ConversationList.tsx │ ├── ConversationItem.tsx │ └── NewChatButton.tsx │ ├── stores/ │ └── conversation-store.ts # Conversation state (keep existing) │ └── lib/ └── chatkit/ └── config.ts # ChatKit configuration utilities
Theming
Dark Mode Support
'use client';
import { ChatKit, useChatKit } from '@openai/chatkit-react'; import { useTheme } from 'next-themes';
export function ThemedChatKit() { const { theme } = useTheme(); const isDark = theme === 'dark';
const { control } = useChatKit({ api: { url: '/chatkit', domainKey: 'local-dev' }, theme: { colorScheme: isDark ? 'dark' : 'light', radius: 'round', color: { grayscale: { hue: 220, tint: 6, shade: isDark ? -1 : -4, }, accent: { primary: isDark ? '#f1f5f9' : '#0f172a', level: 1, }, }, }, });
return <ChatKit control={control} className="h-full w-full" />; }
Tailwind CSS Integration
<ChatKit control={control} className=" h-full w-full [--chatkit-bg:hsl(var(--background))] [--chatkit-text:hsl(var(--foreground))] [--chatkit-primary:hsl(var(--primary))] [--chatkit-border:hsl(var(--border))] " />
Conversation Management
Custom Sidebar with ChatKit
// app/chat/layout.tsx 'use client';
import { ConversationSidebar } from '@/components/conversation/ConversationSidebar';
export default function ChatLayout({ children }: { children: React.ReactNode }) { return ( <div className="flex h-screen"> {/* Custom Conversation Sidebar */} <ConversationSidebar />
{/* ChatKit Area */}
<div className="flex-1 overflow-hidden">
{children}
</div>
</div>
); }
Thread Switching
// When user selects a conversation from sidebar
const handleSelectConversation = (conversationId: number) => {
// Update URL or state
router.push(/chat?thread=${conversationId});
// ChatKit will load the thread automatically if configured };
Environment Variables
Frontend (.env.local)
NEXT_PUBLIC_API_URL=http://localhost:8000
ChatKit Domain Key (REQUIRED for production)
Get from: https://platform.openai.com/settings/organization/security/domain-allowlist
NEXT_PUBLIC_OPENAI_DOMAIN_KEY=your-domain-key-here
Domain Allowlist Configuration (Production)
-
Deploy frontend to get production URL (e.g., https://your-app.vercel.app )
-
Go to: https://platform.openai.com/settings/organization/security/domain-allowlist
-
Add your production domain
-
Copy domain key to NEXT_PUBLIC_OPENAI_DOMAIN_KEY
Note: localhost works without domain allowlist configuration.
Migration from Custom UI
Before (Custom Components)
// OLD: Custom chat interface import { ChatContainer } from '@/components/chat/ChatContainer'; import { MessageList } from '@/components/chat/MessageList'; import { MessageInput } from '@/components/chat/MessageInput';
export default function ChatPage() { return ( <ChatContainer> <MessageList messages={messages} /> <MessageInput onSend={sendMessage} /> </ChatContainer> ); }
After (ChatKit)
// NEW: ChatKit import { ChatKit, useChatKit } from '@openai/chatkit-react';
export default function ChatPage() { const { control } = useChatKit({ api: { url: '/chatkit', domainKey: 'local-dev' }, });
return <ChatKit control={control} className="h-full w-full" />; }
What to Keep
-
ConversationSidebar
-
Better UX than built-in history
-
conversation-store.ts
-
Thread management state
-
Auth integration - User context for ChatKit
What to Remove
-
ChatContainer.tsx
-
Replaced by ChatKit
-
MessageList.tsx
-
Replaced by ChatKit
-
MessageInput.tsx
-
Replaced by ChatKit
-
StreamingMessage.tsx
-
Replaced by ChatKit
-
Custom SSE client (partially) - ChatKit handles streaming
Verification Checklist
-
@openai/chatkit-react installed
-
ChatKit page created at /chat
-
useChatKit hook configured with API URL
-
Theme matches app design system
-
Dark mode works correctly
-
Start screen shows greeting and prompts
-
Custom ConversationSidebar integrated
-
Backend /chatkit endpoint working
-
Streaming responses display correctly
-
Domain allowlist configured (production)
-
Mobile responsive design
Common Issues
ChatKit Not Rendering
// Ensure client-side rendering 'use client';
// Check control is initialized if (!control) return <div>Loading...</div>;
CORS Errors
Backend: Add ChatKit origins to CORS
app.add_middleware( CORSMiddleware, allow_origins=["http://localhost:3000", "https://your-app.vercel.app"], allow_credentials=True, allow_methods=[""], allow_headers=[""], )
SSE Format Issues
Backend: Use correct SSE format for ChatKit
yield f"data: {json.dumps({'type': 'text', 'content': chunk})}\n\n" yield "data: [DONE]\n\n"
See Also
-
REFERENCE.md - Complete API reference
-
examples.md - Full code examples
-
templates/ - Starter templates
-
chatkit-backend skill - Backend integration