Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Builds AI chat interfaces and conversational UI with streaming responses, context management, and multi-modal support. Use when creating ChatGPT-style interfaces, AI assistants, code copilots, or conversational agents. Handles streaming text, token limits, regeneration, feedback loops, tool usage visualization, and AI-specific error patterns. Provides battle-tested components from leading AI products with accessibility and performance built in.
.claude/skills/ancoleman-building-ai-chat/SKILL.md| Model | Eval pass | Runs |
|---|---|---|
| gemini-3.6-flash | 100% | 18 |
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-09 | ✗→✓ | ▲ Improved | 76% | 0% |
| case-01 | ✗→✓ | ▲ Improved | 118% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 58% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 71% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 160% | 0% |
Define the emerging standards for AI/human conversational interfaces in the 2024-2025 AI integration boom. This skill leverages meta-knowledge from building WITH Claude to establish definitive patterns for streaming UX, context management, and multi-modal interactions. As the industry lacks established patterns, this provides the reference implementation others will follow.
Activate this skill when:
Minimal AI chat interface in under 50 lines:
tsximport { useChat } from 'ai/react'; export function MinimalAIChat() { const { messages, input, handleInputChange, handleSubmit, isLoading, stop } = useChat(); return ( <div className="chat-container"> <div className="messages"> {messages.map(m => ( <div key={m.id} className={`message ${m.role}`}> <div className="content">{m.content}</div> </div> ))} {isLoading && <div className="thinking">AI is thinking...</div>} </div> <form onSubmit={handleSubmit} className="input-form"> <input value={input} onChange={handleInputChange} placeholder="Ask anything..." disabled={isLoading} /> {isLoading ? ( <button type="button" onClick={stop}>Stop</button> ) : ( <button type="submit">Send</button> )} </form> </div> ); }
For complete implementation with streaming markdown, see examples/basic-chat.tsx.
Build user, AI, and system message bubbles with streaming support:
tsx// User message <div className="message user"> <div className="content">{message.content}</div> <time className="timestamp">{formatTime(message.timestamp)}</time> </div> // AI message with streaming <div className="message ai"> <Streamdown className="content">{message.content}</Streamdown> {message.isStreaming && <span className="cursor">▊</span>} </div> // System message <div className="message system"> <Icon type="info" /> <span>{message.content}</span> </div>
For markdown rendering, code blocks, and formatting details, see references/message-components.md.
Create rich input experiences with attachments and voice:
tsx<div className="input-container"> <button onClick={attachFile} aria-label="Attach file"> <PaperclipIcon /> </button> <textarea value={input} onChange={handleChange} onKeyDown={handleKeyDown} placeholder="Type a message..." rows={1} style={{ height: textareaHeight }} /> <button onClick={toggleVoice} aria-label="Voice input"> <MicIcon /> </button> <button type="submit" disabled={!input.trim() || isLoading}> <SendIcon /> </button> </div>
Essential controls for AI responses:
tsx<div className="response-controls"> {isStreaming && ( <button onClick={stop} className="stop-btn"> Stop generating </button> )} {!isStreaming && ( <> <button onClick={regenerate} aria-label="Regenerate response"> <RefreshIcon /> Regenerate </button> <button onClick={continueGeneration} aria-label="Continue"> Continue </button> <button onClick={editMessage} aria-label="Edit message"> <EditIcon /> Edit </button> </> )} </div>
Collect user feedback to improve AI responses:
tsx<div className="feedback-controls"> <button onClick={() => sendFeedback('positive')} aria-label="Good response" className={feedback === 'positive' ? 'selected' : ''} > <ThumbsUpIcon /> </button> <button onClick={() => sendFeedback('negative')} aria-label="Bad response" className={feedback === 'negative' ? 'selected' : ''} > <ThumbsDownIcon /> </button> <button onClick={copyToClipboard} aria-label="Copy"> <CopyIcon /> </button> <button onClick={share} aria-label="Share"> <ShareIcon /> </button> </div>
Progressive rendering of AI responses requires special handling:
tsx// Use Streamdown for AI streaming (handles incomplete markdown) import { Streamdown } from '@vercel/streamdown'; // Auto-scroll management useEffect(() => { if (shouldAutoScroll()) { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); } }, [messages]); // Smart auto-scroll heuristic function shouldAutoScroll() { const threshold = 100; // px from bottom const isNearBottom = container.scrollHeight - container.scrollTop - container.clientHeight < threshold; const userNotReading = !hasUserScrolledUp && !isTextSelected; return isNearBottom && userNotReading; }
For complete streaming patterns, auto-scroll behavior, and stop generation, see references/streaming-ux.md.
Communicate token limits clearly to users:
tsx// User-friendly token display function TokenIndicator({ used, total }) { const percentage = (used / total) * 100; const remaining = total - used; return ( <div className="token-indicator"> <div className="progress-bar"> <div className="progress-fill" style={{ width: `${percentage}%` }} /> </div> <span className="token-text"> {percentage > 80 ? `⚠️ About ${Math.floor(remaining / 250)} messages left` : `${Math.floor(remaining / 250)} pages of conversation remaining`} </span> </div> ); }
For summarization strategies, conversation branching, and organization, see references/context-management.md.
Handle images, files, and voice inputs:
tsx// Image upload with preview function ImageUpload({ onUpload }) { return ( <div className="upload-zone" onDrop={handleDrop} onDragOver={preventDefault} > <input type="file" accept="image/*" onChange={handleFileSelect} multiple hidden ref={fileInputRef} /> {previews.map(preview => ( <img key={preview.id} src={preview.url} alt="Upload preview" /> ))} </div> ); }
For complete multi-modal patterns including voice and screen sharing, see references/multi-modal.md.
Handle AI-specific errors gracefully:
tsx// Refusal handling if (response.type === 'refusal') { return ( <div className="error refusal"> <Icon type="info" /> <p>I cannot help with that request.</p> <details> <summary>Why?</summary> <p>{response.reason}</p> </details> <p>Try asking: {response.suggestion}</p> </div> ); } // Rate limit communication if (error.code === 'RATE_LIMIT') { return ( <div className="error rate-limit"> <p>Please wait {error.retryAfter} seconds</p> <CountdownTimer seconds={error.retryAfter} onComplete={retry} /> </div> ); }
For comprehensive error patterns, see references/error-handling.md.
Show when AI is using tools or functions:
tsxfunction ToolUsage({ tool }) { return ( <div className="tool-usage"> <div className="tool-header"> <Icon type={tool.type} /> <span>{tool.name}</span> {tool.status === 'running' && <Spinner />} </div> {tool.status === 'complete' && ( <details> <summary>View details</summary> <pre>{JSON.stringify(tool.result, null, 2)}</pre> </details> )} </div> ); }
For function calling, code execution, and web search patterns, see references/tool-usage.md.
Primary libraries (validated November 2025):
bash# Core AI chat functionality npm install ai @ai-sdk/react @ai-sdk/openai # Streaming markdown rendering npm install @vercel/streamdown # Syntax highlighting npm install react-syntax-highlighter # Security for LLM outputs npm install dompurify
Critical for smooth streaming:
tsx// Memoize message rendering const MemoizedMessage = memo(Message, (prev, next) => prev.content === next.content && prev.isStreaming === next.isStreaming ); // Debounce streaming updates const debouncedUpdate = useMemo( () => debounce(updateMessage, 50), [] ); // Virtual scrolling for long conversations import { VariableSizeList } from 'react-window';
For detailed performance patterns, see references/streaming-ux.md.
Always sanitize AI outputs:
tsximport DOMPurify from 'dompurify'; function SafeAIContent({ content }) { const sanitized = DOMPurify.sanitize(content, { ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'code', 'pre', 'blockquote', 'ul', 'ol', 'li'], ALLOWED_ATTR: ['class'] }); return <Streamdown>{sanitized}</Streamdown>; }
Ensure AI chat is usable by everyone:
tsx// ARIA live regions for screen readers <div role="log" aria-live="polite" aria-relevant="additions"> {messages.map(msg => ( <article key={msg.id} role="article" aria-label={`${msg.role} message`}> {msg.content} </article> ))} </div> // Loading announcements <div role="status" aria-live="polite" className="sr-only"> {isLoading ? 'AI is responding' : ''} </div>
For complete accessibility patterns, see references/accessibility.md.
scripts/parse_stream.js to parse incomplete markdown during streamingscripts/calculate_tokens.py to estimate token usage and context limitsscripts/format_messages.js to format message history for exportreferences/streaming-patterns.md - Complete streaming UX patternsreferences/context-management.md - Token limits and conversation strategiesreferences/multimodal-input.md - Image, file, and voice handlingreferences/feedback-loops.md - User feedback and RLHF patternsreferences/error-handling.md - AI-specific error scenariosreferences/tool-usage.md - Visualizing function calls and tool usereferences/accessibility-chat.md - Screen reader and keyboard supportreferences/library-guide.md - Detailed library documentationreferences/performance-optimization.md - Streaming performance patternsexamples/basic-chat.tsx - Minimal ChatGPT-style interfaceexamples/streaming-chat.tsx - Advanced streaming with memoizationexamples/multimodal-chat.tsx - Images and file uploadsexamples/code-assistant.tsx - IDE-style code copilotexamples/tool-calling-chat.tsx - Function calling visualizationassets/system-prompts.json - Curated prompts for different use casesassets/message-templates.json - Pre-built message componentsassets/error-messages.json - User-friendly error messagesassets/themes.json - Light, dark, and high-contrast themesAll visual styling uses the design-tokens system:
css/* Message bubbles use design tokens */ .message.user { background: var(--message-user-bg, var(--color-primary)); color: var(--message-user-text, var(--color-white)); padding: var(--message-padding, var(--spacing-md)); border-radius: var(--message-border-radius, var(--radius-lg)); } .message.ai { background: var(--message-ai-bg, var(--color-gray-100)); color: var(--message-ai-text, var(--color-text-primary)); }
See skills/design-tokens/ for complete theming system.
This skill provides industry-first solutions for:
This is THE most critical skill because:
Master this skill to lead the AI interface revolution.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-09 | fail→pass | 23,184 | 21,405 | -8% | 1 | 1 | 0% | 4,467 | 7,864 | +76% | 0 | 0 | — |
case-01 | fail→pass | 17,349 | 19,692 | +14% | 1 | 1 | 0% | 3,278 | 7,135 | +118% | 0 | 0 | — |
case-02 | fail→pass | 18,978 | 11,984 | -37% | 1 | 1 | 0% | 3,547 | 5,592 | +58% | 0 | 0 | — |
case-10 | fail→pass | 18,825 | 15,076 | -20% | 1 | 1 | 0% | 3,767 | 6,453 | +71% | 0 | 0 | — |
case-03 | pass→pass | 16,033 | 9,580 | -40% | 1 | 1 | 0% | 2,776 | 5,040 | +82% | 0 | 0 | — |
case-04 | fail→pass | 13,562 | 16,528 | +22% | 1 | 1 | 0% | 2,427 | 6,308 | +160% | 0 | 0 | — |
case-05 | pass→pass | 20,281 | 13,224 | -35% | 1 | 1 | 0% | 3,415 | 5,626 | +65% | 0 | 0 | — |
case-06 | pass→pass | 14,989 | 10,477 | -30% | 1 | 1 | 0% | 2,375 | 5,249 | +121% | 0 | 0 | — |
case-07 | fail→pass | 19,460 | 20,175 | +4% | 1 | 1 | 0% | 3,590 | 7,316 | +104% | 0 | 0 | — |
case-08 | pass→pass | 20,423 | 18,796 | -8% | 1 | 1 | 0% | 4,255 | 7,430 | +75% | 0 | 0 | — |
case-11 | fail→pass | 16,456 | 15,617 | -5% | 1 | 1 | 0% | 3,243 | 6,618 | +104% | 0 | 0 | — |
case-12 | pass→pass | 17,241 | 19,338 | +12% | 1 | 1 | 0% | 3,273 | 7,357 | +125% | 0 | 0 | — |
case-13 | pass→fail | 20,765 | 21,642 | +4% | 1 | 1 | 0% | 4,211 | 8,106 | +92% | 0 | 0 | — |
case-14 | fail→pass | 13,407 | 9,803 | -27% | 1 | 1 | 0% | 2,661 | 5,257 | +98% | 0 | 0 | — |
case-15 | fail→fail | 11,636 | 5,958 | -49% | 1 | 1 | 0% | 2,079 | 4,354 | +109% | 0 | 0 | — |
case-21 | pass→pass | 10,336 | 8,836 | -15% | 1 | 1 | 0% | 1,965 | 5,083 | +159% | 0 | 0 | — |
case-16 | fail→fail | 10,036 | 7,326 | -27% | 1 | 1 | 0% | 1,626 | 4,546 | +180% | 0 | 0 | — |
case-17 | fail→fail | 18,010 | 19,306 | +7% | 1 | 1 | 0% | 3,626 | 7,518 | +107% | 0 | 0 | — |
case-18 | fail→fail | 16,173 | 14,425 | -11% | 1 | 1 | 0% | 2,588 | 5,814 | +125% | 0 | 0 | — |
case-19 | pass→pass | 7,282 | 7,596 | +4% | 1 | 1 | 0% | 1,412 | 5,005 | +254% | 0 | 0 | — |
case-20 | pass→pass | 21,020 | 10,603 | -50% | 1 | 1 | 0% | 2,118 | 5,445 | +157% | 0 | 0 | — |
case-22 | pass→pass | 16,484 | 16,676 | +1% | 1 | 1 | 0% | 3,120 | 6,416 | +106% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +32 percentage points is the difference between those two pass rates over the 22 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.