AI SDK Elements
Overview
AI SDK Elements is a React component library built on shadcn/ui for AI-native applications. Part of Vercel's AI SDK ecosystem.
Requirements:
-
React 19 (no forwardRef patterns)
-
Tailwind CSS 4
-
shadcn/ui configured
Docs: https://ai-sdk.dev/elements
Quick Reference
Install all components
npx ai-elements@latest
Install specific component
npx ai-elements@latest add conversation npx ai-elements@latest add message npx ai-elements@latest add reasoning
Alternative (shadcn CLI)
npx shadcn@latest add @ai-elements/conversation
Components install to @/components/ai-elements/
Core Components
Conversation (Chat Container)
Main wrapper with auto-scroll to bottom.
import { Conversation, ConversationContent, ConversationEmptyState, ConversationScrollButton, } from '@/components/ai-elements/conversation';
<Conversation className="relative h-full"> <ConversationContent> {messages.length === 0 ? ( <ConversationEmptyState icon={<MessageSquareIcon />} title="Start a conversation" description="Ask me anything" /> ) : ( messages.map((msg) => ( <Message key={msg.id} from={msg.role}> <MessageContent>{msg.content}</MessageContent> </Message> )) )} </ConversationContent> <ConversationScrollButton /> </Conversation>
Message
Individual chat message display.
import { Message, MessageContent } from '@/components/ai-elements/message';
<Message from="user"> <MessageContent>Hello, can you help me?</MessageContent> </Message>
<Message from="assistant"> <MessageContent>{response}</MessageContent> </Message>
Prompt Input
User input for chat.
import { PromptInput } from '@/components/ai-elements/prompt-input';
<PromptInput value={input} onChange={(e) => setInput(e.target.value)} onSubmit={handleSubmit} placeholder="Type a message..." />
AI Reasoning Components
Reasoning (Collapsible Thinking)
Auto-opens during streaming, collapses when done.
import { Reasoning, ReasoningTrigger, ReasoningContent, } from '@/components/ai-elements/reasoning';
<Reasoning isStreaming={isStreaming} className="w-full"> <ReasoningTrigger title="Thinking..." /> <ReasoningContent>{reasoningText}</ReasoningContent> </Reasoning>
Chain of Thought
Visual step-by-step reasoning with search results, images, progress.
import { ChainOfThought } from '@/components/ai-elements/chain-of-thought';
<ChainOfThought> <ChainOfThoughtStep> <ChainOfThoughtIcon><SearchIcon /></ChainOfThoughtIcon> <ChainOfThoughtContent> Searching for profiles... <ChainOfThoughtLinks> <ChainOfThoughtLink href="https://x.com">x.com</ChainOfThoughtLink> <ChainOfThoughtLink href="https://github.com">github.com</ChainOfThoughtLink> </ChainOfThoughtLinks> </ChainOfThoughtContent> </ChainOfThoughtStep> </ChainOfThought>
Plan & Task
Display agent plans and individual tasks.
import { Plan, Task } from '@/components/ai-elements/plan';
<Plan title="Implementation Plan"> <Task status="completed">Set up project structure</Task> <Task status="in_progress">Implement core features</Task> <Task status="pending">Write tests</Task> </Plan>
Interactivity Components
Confirmation (Tool Approval)
Manage tool execution approval workflows.
import { Confirmation, ConfirmationRequest, ConfirmationAccepted, ConfirmationRejected, ConfirmationActions, ConfirmationAction, } from '@/components/ai-elements/confirmation';
<Confirmation approval={tool.approval} state={tool.state}> <ConfirmationRequest> This tool wants to delete: <code>{tool.input?.filePath}</code> <br />Do you approve? </ConfirmationRequest> <ConfirmationAccepted>File deleted successfully</ConfirmationAccepted> <ConfirmationRejected>Action cancelled</ConfirmationRejected> <ConfirmationActions> <ConfirmationAction onClick={() => respondToConfirmationRequest({ approvalId, approved: false })}> Reject </ConfirmationAction> <ConfirmationAction onClick={() => respondToConfirmationRequest({ approvalId, approved: true })}> Approve </ConfirmationAction> </ConfirmationActions> </Confirmation>
Suggestion (Quick Prompts)
Horizontal row of clickable suggestions.
import { Suggestions, Suggestion } from '@/components/ai-elements/suggestion';
const prompts = [ 'How do I get started?', 'What can you help with?', 'Show me examples', ];
<Suggestions> {prompts.map((prompt) => ( <Suggestion key={prompt} suggestion={prompt} onClick={(text) => setInput(text)} /> ))} </Suggestions>
Tool
Display tool calls and results.
import { Tool, ToolContent, ToolResult } from '@/components/ai-elements/tool';
<Tool name="search_web"> <ToolContent> Searching for: {tool.args.query} </ToolContent> <ToolResult> {tool.result} </ToolResult> </Tool>
Checkpoint (Restore Points)
Mark and restore conversation history.
import { Checkpoint, CheckpointIcon, CheckpointTrigger, } from '@/components/ai-elements/checkpoint';
<Checkpoint> <CheckpointIcon /> <CheckpointTrigger onClick={() => restoreToCheckpoint(index)}> Restore to this point </CheckpointTrigger> </Checkpoint>
Citation Components
Sources
Collapsible source citations.
import { Sources, SourcesTrigger, SourcesContent, Source, } from '@/components/ai-elements/sources';
<Sources> <SourcesTrigger count={3} /> <SourcesContent> <Source href="https://docs.example.com/api" title="API Documentation" /> <Source href="https://example.com/guide" title="Getting Started Guide" /> <Source href="https://example.com/faq" title="FAQ" /> </SourcesContent> </Sources>
Inline Citation
Citations within text content.
import { InlineCitation } from '@/components/ai-elements/inline-citation';
<p> According to the documentation <InlineCitation href="https://docs.example.com" index={1} /> , you should... </p>
Loading Components
Queue
Message queue/loading state.
import { Queue } from '@/components/ai-elements/queue';
<Queue>Processing your request...</Queue>
Shimmer
Skeleton loading placeholder.
import { Shimmer } from '@/components/ai-elements/shimmer';
{isLoading && <Shimmer className="h-20 w-full" />}
Utility Components
Model Selector
Switch between AI models.
import { ModelSelector } from '@/components/ai-elements/model-selector';
<ModelSelector models={['gpt-4', 'claude-3', 'gemini-pro']} selected={model} onSelect={setModel} />
Context
Display context information.
import { Context } from '@/components/ai-elements/context';
<Context title="Current Context"> Working on: Project Alpha Files: 3 selected </Context>
Complete Chat Example
'use client';
import { useState } from 'react'; import { useChat } from 'ai/react'; import { Conversation, ConversationContent, ConversationEmptyState, ConversationScrollButton, } from '@/components/ai-elements/conversation'; import { Message, MessageContent } from '@/components/ai-elements/message'; import { PromptInput } from '@/components/ai-elements/prompt-input'; import { Reasoning, ReasoningTrigger, ReasoningContent } from '@/components/ai-elements/reasoning'; import { Suggestions, Suggestion } from '@/components/ai-elements/suggestion'; import { Sources, SourcesTrigger, SourcesContent, Source } from '@/components/ai-elements/sources';
export function Chat() { const { messages, input, setInput, handleSubmit, isLoading } = useChat();
const suggestions = [ 'What can you help me with?', 'Tell me about AI SDK', 'How do I get started?', ];
return ( <div className="flex h-screen flex-col"> <Conversation className="flex-1"> <ConversationContent className="p-4"> {messages.length === 0 ? ( <> <ConversationEmptyState title="Welcome!" description="Ask me anything to get started" /> <Suggestions className="mt-4"> {suggestions.map((s) => ( <Suggestion key={s} suggestion={s} onClick={setInput} /> ))} </Suggestions> </> ) : ( messages.map((msg) => ( <Message key={msg.id} from={msg.role}> <MessageContent>{msg.content}</MessageContent>
{/* Show reasoning if available */}
{msg.reasoning && (
<Reasoning isStreaming={isLoading && msg.id === messages.at(-1)?.id}>
<ReasoningTrigger />
<ReasoningContent>{msg.reasoning}</ReasoningContent>
</Reasoning>
)}
{/* Show sources if available */}
{msg.sources?.length > 0 && (
<Sources>
<SourcesTrigger count={msg.sources.length} />
<SourcesContent>
{msg.sources.map((src, i) => (
<Source key={i} href={src.url} title={src.title} />
))}
</SourcesContent>
</Sources>
)}
</Message>
))
)}
</ConversationContent>
<ConversationScrollButton />
</Conversation>
<form onSubmit={handleSubmit} className="border-t p-4">
<PromptInput
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Type a message..."
disabled={isLoading}
/>
</form>
</div>
); }
Styling
All components accept className prop for Tailwind customization:
<Message className="bg-muted/50 rounded-lg p-4"> <MessageContent className="text-sm">{content}</MessageContent> </Message>
<Reasoning className="border-l-2 border-blue-500 pl-4"> <ReasoningTrigger className="text-blue-600" /> <ReasoningContent className="text-muted-foreground" /> </Reasoning>
Integration with AI SDK
These components work seamlessly with Vercel AI SDK hooks:
import { useChat } from 'ai/react'; import { useCompletion } from 'ai/react'; import { useAssistant } from 'ai/react';
// useChat for conversational interfaces const { messages, input, handleSubmit } = useChat();
// useCompletion for single completions const { completion, complete } = useCompletion();
// useAssistant for OpenAI Assistants const { messages, submitMessage } = useAssistant();
Reference
See references/component-api.md for complete props documentation.