|$ curl https://forge-ai.dev/api/markdown?path=docs/ai/ai-ui
$cat docs/ai-in-user-interfaces.md
updated Recently·35 min read·published

AI in User Interfaces

AIIntermediate🎯Free Tools
Introduction

Integrating AI into user interfaces requires thoughtful design beyond raw API calls. The best AI-powered UIs feel instant, transparent, and reliable — masking the latency and complexity of LLM inference behind familiar interaction patterns.

This guide covers practical patterns for embedding AI into web interfaces: search augmentation, autocomplete suggestions, chat layouts, generative form outputs, and the UX principles that make AI feel natural rather than gimmicky.

AI Autocomplete

AI autocomplete goes beyond prefix matching to understand intent and context. It uses streaming to show suggestions as they're generated, with keyboard navigation for selection.

AIAutocomplete.tsx
TypeScript
1// AI autocomplete with keyboard navigation
2"use client";
3import { useState, useRef, useEffect } from "react";
4
5function AIAutocomplete() {
6 const [input, setInput] = useState("");
7 const [suggestions, setSuggestions] = useState<string[]>([]);
8 const [selectedIndex, setSelectedIndex] = useState(-1);
9 const [isStreaming, setIsStreaming] = useState(false);
10 const inputRef = useRef<HTMLInputElement>(null);
11
12 useEffect(() => {
13 if (input.length < 3) { setSuggestions([]); return; }
14
15 const controller = new AbortController();
16 const timer = setTimeout(async () => {
17 setIsStreaming(true);
18 const res = await fetch("/api/autocomplete", {
19 method: "POST",
20 headers: { "Content-Type": "application/json" },
21 body: JSON.stringify({ prompt: input }),
22 signal: controller.signal,
23 });
24
25 const reader = res.body!.getReader();
26 const decoder = new TextDecoder();
27 const items: string[] = [];
28
29 while (true) {
30 const { done, value } = await reader.read();
31 if (done) break;
32 const chunk = decoder.decode(value, { stream: true });
33 const lines = chunk.split("\n");
34
35 for (const line of lines) {
36 if (line.startsWith("data: ")) {
37 try {
38 const { suggestion } = JSON.parse(line.slice(6));
39 items.push(suggestion);
40 setSuggestions([...items]);
41 } catch {}
42 }
43 }
44 }
45 setIsStreaming(false);
46 }, 250);
47
48 return () => { clearTimeout(timer); controller.abort(); };
49 }, [input]);
50
51 const handleKeyDown = (e: React.KeyboardEvent) => {
52 if (e.key === "ArrowDown") {
53 e.preventDefault();
54 setSelectedIndex((i) => Math.min(i + 1, suggestions.length - 1));
55 } else if (e.key === "ArrowUp") {
56 e.preventDefault();
57 setSelectedIndex((i) => Math.max(i - 1, -1));
58 } else if (e.key === "Enter" && selectedIndex >= 0) {
59 setInput(suggestions[selectedIndex]);
60 setSuggestions([]);
61 } else if (e.key === "Escape") {
62 setSuggestions([]);
63 }
64 };
65
66 return (
67 <div className="relative">
68 <input ref={inputRef} value={input} onChange={(e) => setInput(e.target.value)} onKeyDown={handleKeyDown} />
69 {suggestions.length > 0 && (
70 <ul className="dropdown">
71 {suggestions.map((s, i) => (
72 <li key={i} className={i === selectedIndex ? "selected" : ""}>{s}</li>
73 ))}
74 </ul>
75 )}
76 </div>
77 );
78}

best practice

Show a subtle loading indicator during autocomplete streaming (spinner or pulsing dot). Users need feedback that the AI is working, not just a silent delay.
Chat Interface Patterns

Chat interfaces are the most common AI UI pattern. Key considerations include message layout, streaming rendering, action buttons, copy functionality, and conversation management.

ChatInterface.tsx
TypeScript
1// Production chat component with all features
2"use client";
3import { useState, useRef, useEffect } from "react";
4
5interface Message {
6 id: string;
7 role: "user" | "assistant";
8 content: string;
9 timestamp: Date;
10}
11
12function ChatInterface() {
13 const [messages, setMessages] = useState<Message[]>([]);
14 const [input, setInput] = useState("");
15 const [isStreaming, setIsStreaming] = useState(false);
16 const messagesEndRef = useRef<HTMLDivElement>(null);
17
18 // Auto-scroll to bottom
19 useEffect(() => {
20 messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
21 }, [messages]);
22
23 const sendMessage = async () => {
24 if (!input.trim() || isStreaming) return;
25
26 const userMsg: Message = {
27 id: crypto.randomUUID(),
28 role: "user",
29 content: input.trim(),
30 timestamp: new Date(),
31 };
32
33 setMessages((prev) => [...prev, userMsg]);
34 setInput("");
35 setIsStreaming(true);
36
37 const aiMsg: Message = {
38 id: crypto.randomUUID(),
39 role: "assistant",
40 content: "",
41 timestamp: new Date(),
42 };
43 setMessages((prev) => [...prev, aiMsg]);
44
45 const res = await fetch("/api/chat", {
46 method: "POST",
47 headers: { "Content-Type": "application/json" },
48 body: JSON.stringify({ messages: [...messages, userMsg] }),
49 });
50
51 const reader = res.body!.getReader();
52 const decoder = new TextDecoder();
53 let content = "";
54
55 while (true) {
56 const { done, value } = await reader.read();
57 if (done) break;
58 content += decoder.decode(value, { stream: true });
59 setMessages((prev) =>
60 prev.map((m) => (m.id === aiMsg.id ? { ...m, content } : m))
61 );
62 }
63 setIsStreaming(false);
64 };
65
66 return (
67 <div className="flex flex-col h-full">
68 <div className="flex-1 overflow-y-auto p-4 space-y-4">
69 {messages.map((msg) => (
70 <MessageBubble key={msg.id} message={msg} />
71 ))}
72 <div ref={messagesEndRef} />
73 </div>
74 <div className="border-t p-4 flex gap-2">
75 <input value={input} onChange={(e) => setInput(e.target.value)} onKeyDown={(e) => e.key === "Enter" && sendMessage()} className="flex-1" placeholder="Ask anything..." disabled={isStreaming} />
76 <button onClick={sendMessage} disabled={isStreaming || !input.trim()}>
77 {isStreaming ? "Generating..." : "Send"}
78 </button>
79 </div>
80 </div>
81 );
82}

info

Disable the send button during streaming and while the input is empty. Preventing duplicate messages and empty submissions eliminates two common sources of frustration in chat UIs.
Generative UI

Generative UI goes beyond text — the AI generates interactive UI components (charts, forms, code blocks, data tables) as part of its response. This transforms chat from Q&A into a rich application interface.

GenerativeUI.tsx
TypeScript
1// AI generates structured UI components
2interface AIToolCall {
3 type: "chart" | "table" | "code" | "form" | "text";
4 data: Record<string, unknown>;
5}
6
7// Server: detect tool calls and return structured data
8// Client: render appropriate component based on type
9function GenerativeMessage({ content, toolCalls }: { content: string; toolCalls?: AIToolCall[] }) {
10 return (
11 <div className="message space-y-4">
12 <ReactMarkdown>{content}</ReactMarkdown>
13 {toolCalls?.map((call, i) => {
14 switch (call.type) {
15 case "chart":
16 return <AIChart key={i} data={call.data} />;
17 case "table":
18 return <DataTable key={i} rows={call.data.rows} columns={call.data.columns} />;
19 case "code":
20 return <CodeBlock key={i} language={call.data.language} code={call.data.code} />;
21 case "form":
22 return <GeneratedForm key={i} fields={call.data.fields} />;
23 default:
24 return null;
25 }
26 })}
27 </div>
28 );
29}
30
31// Example: AI generates a data visualization
32function AIChart({ data }: { data: { labels: string[]; values: number[] } }) {
33 return (
34 <div className="chart-container p-4 border rounded-lg">
35 <ResponsiveContainer width="100%" height={300}>
36 <BarChart data={data.labels.map((l, i) => ({ name: l, value: data.values[i] }))}>
37 <XAxis dataKey="name" />
38 <YAxis />
39 <Bar dataKey="value" fill="#00FF41" />
40 </BarChart>
41 </ResponsiveContainer>
42 </div>
43 );
44}

best practice

Always validate AI-generated UI data on the client before rendering. Never trust raw LLM output for interactive components — use schema validation (Zod) to ensure data shapes match expected types.
UX Patterns & Principles

Great AI UIs follow specific UX principles that manage user expectations, handle uncertainty, and build trust. These patterns are critical for production adoption.

Loading States

Show clear loading indicators for the full lifecycle: request sent, first token received, streaming complete. Users should never wonder if something is happening.

LoadingStates.tsx
TypeScript
1// Loading state management
2type LoadingPhase = "idle" | "connecting" | "streaming" | "complete" | "error";
3
4function LoadingIndicator({ phase }: { phase: LoadingPhase }) {
5 switch (phase) {
6 case "connecting":
7 return <div className="flex items-center gap-2 text-sm text-gray-400"><Spinner /> Connecting...</div>;
8 case "streaming":
9 return <div className="flex items-center gap-2 text-sm text-gray-400"><PulsingDot /> Generating...</div>;
10 case "error":
11 return <div className="text-sm text-red-400">Failed. <button onClick={retry}>Retry</button></div>;
12 default:
13 return null;
14 }
15}

Confidence Indicators

Show AI confidence through visual cues. Dim uncertain responses, highlight high-confidence answers, and provide source attribution.

Confidence.tsx
TypeScript
1// Confidence-based styling
2function AIResponse({ text, confidence, sources }: Props) {
3 const opacity = Math.max(0.5, confidence);
4 return (
5 <div style={{ opacity }} className="p-4 rounded-lg border">
6 <p>{text}</p>
7 {sources.length > 0 && (
8 <div className="mt-2 text-xs text-gray-500">
9 Sources: {sources.map((s) => (
10 <a key={s.url} href={s.url} className="underline">{s.title}</a>
11 ))}
12 </div>
13 )}
14 </div>
15 );
16}

Input Prompts & Suggestions

Guide users with example prompts, category chips, and context-aware suggestions. The empty state is the most important moment for AI UX.

EmptyState.tsx
TypeScript
1// Suggestion chips for empty chat state
2const suggestions = [
3 "Explain this code",
4 "Write a unit test",
5 "Debug this error",
6 "Optimize performance",
7];
8
9function EmptyState({ onSelect }: { onSelect: (s: string) => void }) {
10 return (
11 <div className="flex flex-col items-center justify-center h-full gap-6">
12 <h2 className="text-xl font-semibold">How can I help?</h2>
13 <div className="flex flex-wrap gap-2 justify-center">
14 {suggestions.map((s) => (
15 <button key={s} onClick={() => onSelect(s)} className="px-4 py-2 border rounded-full text-sm hover:bg-gray-800 transition">
16 {s}
17 </button>
18 ))}
19 </div>
20 </div>
21 );
22}

warning

Never show raw AI errors to users. Translate technical errors (rate limits, timeouts, context length exceeded) into user-friendly messages with actionable next steps.
$Blueprint — Engineering Documentation·Section ID: AI-UI-01·Revision: 1.0