| name | chatkit-frontend |
| description | Build production-ready chat UI with OpenAI ChatKit React components. Handles ChatKit installation, useChatKit hook configuration, theming, streaming display, conversation sidebar, and rich widgets. Use when implementing chat interface, migrating from custom UI to ChatKit, or adding AI chat features for Phase 3. |
| allowed-tools | Bash, Write, Read, Edit, Glob, Grep |
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 historyconversation-store.ts- Thread management state- Auth integration - User context for ChatKit
What to Remove
ChatContainer.tsx- Replaced by ChatKitMessageList.tsx- Replaced by ChatKitMessageInput.tsx- Replaced by ChatKitStreamingMessage.tsx- Replaced by ChatKit- Custom SSE client (partially) - ChatKit handles streaming
Verification Checklist
-
@openai/chatkit-reactinstalled - ChatKit page created at
/chat -
useChatKithook configured with API URL - Theme matches app design system
- Dark mode works correctly
- Start screen shows greeting and prompts
- Custom ConversationSidebar integrated
- Backend
/chatkitendpoint 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