.claude/skills/chatkit-frontend/SKILL.md
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.
npx skillsauth add maneeshanif/todo-spec-driven chatkit-frontendInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Production-ready skill for implementing OpenAI ChatKit in Next.js applications.
Official Documentation (ALWAYS verify before implementation):
OpenAI ChatKit is a batteries-included framework for building AI-powered chat experiences with:
┌─────────────────────────────────────────────────────────────────────────┐
│ 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 │
└─────────────────────────────────────────┘
cd frontend
npm install @openai/chatkit-react
# Check package.json
grep chatkit package.json
// 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>
);
}
// 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>
);
}
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
'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" />;
}
<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))]
"
/>
// 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>
);
}
// 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
};
# 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
https://your-app.vercel.app)NEXT_PUBLIC_OPENAI_DOMAIN_KEYNote: localhost works without domain allowlist configuration.
// 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>
);
}
// 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" />;
}
ConversationSidebar - Better UX than built-in historyconversation-store.ts - Thread management stateChatContainer.tsx - Replaced by ChatKitMessageList.tsx - Replaced by ChatKitMessageInput.tsx - Replaced by ChatKitStreamingMessage.tsx - Replaced by ChatKit@openai/chatkit-react installed/chatuseChatKit hook configured with API URL/chatkit endpoint working// Ensure client-side rendering
'use client';
// Check control is initialized
if (!control) return <div>Loading...</div>;
# 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=["*"],
)
# Backend: Use correct SSE format for ChatKit
yield f"data: {json.dumps({'type': 'text', 'content': chunk})}\n\n"
yield "data: [DONE]\n\n"
tools
Implement WebSocket service for real-time task synchronization across clients. Use when building real-time updates for Phase 5. (project)
data-ai
Implement Urdu language support with RTL layout, translations, and AI responses in Urdu. Bonus feature (+100 points) for Phase 5. (project)
development
DEPRECATED - Use chatkit-backend skill instead. SSE streaming is now part of the chatkit-backend skill for ChatKit integration.
development
Install and configure Shadcn/ui component library with Radix UI primitives, Aceternity UI effects, set up components, and manage the component registry. Use when adding Shadcn/ui to a Next.js project or installing specific UI components for Phase 2.