Templates
Complete, copy-paste patterns built entirely from Slipstream UI components. Each one is stateless — you own the data flow and transport.
Streaming chat
The core pattern: streaming markdown responses, stop-generation, auto-scroll pinning, and feedback actions. ~80 lines.
Response + MessageStop buttonScroll pillMessageActions
Live demo — try it
U
You
Show me the streaming chat pattern
'use client';
import { useRef, useState } from 'react';
import {
Conversation,
MessageActions,
PromptInput,
Response,
} from '@slipstream-ui/ai';
import type { Message } from '@slipstream-ui/ai';
export function StreamingChat() {
const [messages, setMessages] = useState<Message[]>([]);
const [streamingId, setStreamingId] = useState<string | null>(null);
const abortRef = useRef<AbortController | null>(null);
async function send(text: string) {
const controller = new AbortController();
abortRef.current = controller;
// Your transport — any endpoint that streams text chunks.
const response = await fetch('/api/chat', {
method: 'POST',
body: JSON.stringify({ prompt: text }),
signal: controller.signal,
});
const id = crypto.randomUUID();
setMessages((prev) => [...prev, { id, role: 'assistant', content: '' }]);
setStreamingId(id);
const reader = response.body!.getReader();
const decoder = new TextDecoder();
let content = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
content += decoder.decode(value, { stream: true });
setMessages((prev) =>
prev.map((m) => (m.id === id ? { ...m, content } : m))
);
}
setStreamingId(null);
}
return (
<div className="flex flex-col h-[600px]">
<Conversation
messages={messages}
streamingMessageId={streamingId ?? undefined}
renderContent={(content) => <Response parseIncompleteMarkdown>{content}</Response>}
className="flex-1"
/>
<div className="pl-12">
{messages.at(-1)?.role === 'assistant' && (
<MessageActions
content={messages.at(-1)!.content}
onFeedback={(rating) => analytics.track(rating)}
onRegenerate={() => send('Regenerate')}
/>
)}
</div>
<PromptInput
onSubmit={send}
isLoading={streamingId !== null}
onStop={() => abortRef.current?.abort()}
/>
</div>
);
}Agent dashboard
Tool calls with human-in-the-loop approval, reasoning steps, and task progress — the pattern for autonomous agents.
Confirmation gateToolCall lifecycleReasoning panelChainThought
'use client';
import { useState } from 'react';
import {
ChainThought,
Confirmation,
Reasoning,
ToolCall,
} from '@slipstream-ui/ai';
import type { ToolCall as ToolCallType } from '@slipstream-ui/ai';
export function AgentActivity({ toolCall }: { toolCall: ToolCallType }) {
const [approved, setApproved] = useState(false);
return (
<div className="space-y-4 max-w-xl">
{/* Pause destructive actions until approved */}
{toolCall.name === 'delete_files' && !approved ? (
<Confirmation
title="Delete all files in /tmp?"
description="This affects 14 files and cannot be undone."
toolName={toolCall.name}
onApprove={() => setApproved(true)}
onReject={() => console.log('cancelled')}
/>
) : (
<ToolCall toolCall={toolCall} />
)}
<Reasoning
steps={[
{ id: '1', content: 'Parse user intent' },
{ id: '2', content: 'Select delete_files tool' },
{ id: '3', content: 'Await user approval' },
]}
/>
<ChainThought
steps={[
{ id: 't1', content: 'Scan directory', status: 'completed' },
{ id: 't2', content: 'Request approval', status: 'pending' },
]}
/>
</div>
);
}RAG citations
Grounded answers with inline citations and a sources footer — the pattern for search and knowledge-base products.
InlineCitation chipsSources footerSuggestions
import {
InlineCitation,
Sources,
Suggestions,
} from '@slipstream-ui/ai';
import type { SourceItem } from '@slipstream-ui/ai';
interface GroundedAnswer {
answer: string; // markdown with {{citation:N}} markers
sources: SourceItem[];
followUps: { id: string; text: string }[];
}
// Resolve citation markers to InlineCitation chips at render time.
function renderAnswer(answer: string, sources: SourceItem[]) {
return answer.split(/\{\{citation:(\d+)\}\}/).map((part, i) => {
const source = sources[Number(part) - 1];
if (i % 2 === 1 && source) {
return <InlineCitation key={i} source={source} />;
}
return part;
});
}
export function GroundedResponse({ result }: { result: GroundedAnswer }) {
return (
<div>
<p>{renderAnswer(result.answer, result.sources)}</p>
<Sources sources={result.sources} maxDisplay={3} />
<Suggestions suggestions={result.followUps} />
</div>
);
}