Documentation
AI Elements are React components for AI SDK chat UIs: messages, prompts, tool calls, reasoning, sources, and artifacts. Patterns that use them ship the files under @/components/ai-elements.
Peer dependency:
pnpm add ai @ai-sdk/react
Components
Message
One message in a conversation.
import {
Message,
MessageAvatar,
MessageContent,
} from "@/components/ai-elements/message";
export function ChatMessage({ message }) {
return (
<Message from={message.role}>
<MessageAvatar src={message.avatar} name={message.name} />
<MessageContent variant="contained">
{message.parts.map((part, index) =>
part.type === "text" ? <span key={index}>{part.text}</span> : null
)}
</MessageContent>
</Message>
);
}Props:
from:"user" | "assistant"- The role of the message sendervariant:"contained" | "flat"- Visual style variant
MessageContent
Message body. Styles differ for user and assistant roles.
<MessageContent variant="contained">
<p>Your message content here</p>
</MessageContent>MessageAvatar
Avatar for the sender.
<MessageAvatar src="/path/to/avatar.jpg" name="John Doe" />Conversation
Chat scroll container with auto-scroll.
import {
Conversation,
ConversationContent,
} from "@/components/ai-elements/conversation";
export function ChatInterface() {
return (
<Conversation>
<ConversationContent>{/* Your messages here */}</ConversationContent>
</Conversation>
);
}ConversationEmptyState
Empty chat placeholder.
import { ConversationEmptyState } from "@/components/ai-elements/conversation";
<ConversationEmptyState
title="No messages yet"
description="Start a conversation to see messages here"
icon={<ChatIcon />}
/>;ConversationScrollButton
Jump back to the latest message after scrolling up.
import { ConversationScrollButton } from "@/components/ai-elements/conversation";
<ConversationScrollButton />;PromptInput
Composer for chat: textarea, attachments, and submit.
import {
PromptInput,
PromptInputAttachment,
PromptInputAttachments,
PromptInputBody,
PromptInputFooter,
PromptInputSubmit,
PromptInputTextarea,
} from "@/components/ai-elements/prompt-input";
export function ChatInput({ onSubmit }) {
return (
<PromptInput onSubmit={onSubmit}>
<PromptInputBody>
<PromptInputAttachments>
{(attachment) => <PromptInputAttachment data={attachment} />}
</PromptInputAttachments>
<PromptInputTextarea placeholder="What would you like to know?" />
</PromptInputBody>
<PromptInputFooter>
<PromptInputSubmit />
</PromptInputFooter>
</PromptInput>
);
}Features:
- File drag & drop support
- Image preview
- Auto-resizing textarea
- Keyboard shortcuts (Enter to send, Shift+Enter for new line)
- Paste file support
PromptInputSubmit
Submit control. Icon follows status.
<PromptInputSubmit status="streaming" />Status options:
"submitted"- Shows loading spinner"streaming"- Shows stop icon"error"- Shows error iconundefined- Shows send icon
Response
Renders streamed assistant text.
import { Response } from "@/components/ai-elements/response";
export function AIResponse({ content }) {
return <Response>{content}</Response>;
}Loader
Spinner while a reply is pending.
import { Loader } from "@/components/ai-elements/loader";
<Loader size={16} />;Suggestions
Horizontal list of suggested prompts.
import { Suggestion, Suggestions } from "@/components/ai-elements/suggestion";
export function SuggestionList({ suggestions, onSuggestionClick }) {
return (
<Suggestions>
{suggestions.map((suggestion) => (
<Suggestion
key={suggestion}
suggestion={suggestion}
onClick={onSuggestionClick}
/>
))}
</Suggestions>
);
}Tool
Collapsible panel for one tool call (input, output, error).
import {
Tool,
ToolContent,
ToolHeader,
ToolInput,
ToolOutput,
} from "@/components/ai-elements/tool";
export function ToolExecution({ tool }) {
return (
<Tool>
<ToolHeader title={tool.name} type={tool.type} state={tool.state} />
<ToolContent>
<ToolInput input={tool.input} />
<ToolOutput output={tool.output} errorText={tool.errorText} />
</ToolContent>
</Tool>
);
}Tool States:
"input-streaming"- Tool is receiving input"input-available"- Tool is processing"output-available"- Tool completed successfully"output-error"- Tool encountered an error
GeneratedImage
Shows a model-generated image from base64 data.
import { GeneratedImage } from "@/components/ai-elements/image";
<GeneratedImage
base64={imageData.base64}
mediaType={imageData.mediaType}
alt="AI generated image"
/>;Actions
Row of message actions (retry, copy, and similar).
import { Action, Actions } from "@/components/ai-elements/actions";
export function MessageActions() {
return (
<Actions>
<Action tooltip="Retry message" label="Retry">
<RetryIcon />
</Action>
<Action tooltip="Copy message" label="Copy">
<CopyIcon />
</Action>
</Actions>
);
}Props:
tooltip: Optional tooltip textlabel: Accessibility labelvariant: Button variantsize: Button size
Context
Token usage and estimated cost for the current model.
import {
Context,
ContextContent,
ContextContentBody,
ContextContentFooter,
ContextContentHeader,
ContextInputUsage,
ContextOutputUsage,
ContextTrigger,
} from "@/components/ai-elements/context";
export function ContextUsage({ usage, modelId }) {
return (
<Context
usedTokens={usage.inputTokens + usage.outputTokens}
maxTokens={8000}
usage={usage}
modelId={modelId}
>
<ContextTrigger />
<ContextContent>
<ContextContentHeader />
<ContextContentBody>
<ContextInputUsage />
<ContextOutputUsage />
</ContextContentBody>
<ContextContentFooter />
</ContextContent>
</Context>
);
}Features:
- Token usage visualization
- Cost estimation
- Progress indicators
- Hover card display
Artifact
Shell for generated artifacts (tables, charts, documents).
import {
Artifact,
ArtifactAction,
ArtifactActions,
ArtifactClose,
ArtifactContent,
ArtifactDescription,
ArtifactHeader,
ArtifactTitle,
} from "@/components/ai-elements/artifact";
export function ArtifactDisplay({ title, description, children }) {
return (
<Artifact>
<ArtifactHeader>
<div>
<ArtifactTitle>{title}</ArtifactTitle>
<ArtifactDescription>{description}</ArtifactDescription>
</div>
<ArtifactActions>
<ArtifactAction tooltip="Download" icon={DownloadIcon} />
<ArtifactAction tooltip="Share" icon={ShareIcon} />
<ArtifactClose />
</ArtifactActions>
</ArtifactHeader>
<ArtifactContent>{children}</ArtifactContent>
</Artifact>
);
}Sources
Citation list for retrieved or searched sources.
import {
Source,
Sources,
SourcesContent,
SourcesTrigger,
} from "@/components/ai-elements/sources";
export function SourceCitations({ sources }) {
return (
<Sources>
<SourcesTrigger count={sources.length} />
<SourcesContent>
{sources.map((source, index) => (
<Source key={index} href={source.url} title={source.title} />
))}
</SourcesContent>
</Sources>
);
}Branch
Switch between alternate assistant replies.
import {
Branch,
BranchMessages,
BranchNext,
BranchPage,
BranchPrevious,
BranchSelector,
} from "@/components/ai-elements/branch";
export function BranchingChat({ messages }) {
return (
<Branch defaultBranch={0}>
<BranchMessages>
{messages.map((branch, index) => (
<div key={index}>
{branch.map((message) => (
<Message key={message.id} from={message.role}>
<MessageContent>
{message.parts.map((part, partIndex) =>
part.type === "text" ? (
<span key={partIndex}>{part.text}</span>
) : null
)}
</MessageContent>
</Message>
))}
</div>
))}
</BranchMessages>
<BranchSelector from="assistant">
<BranchPrevious />
<BranchPage />
<BranchNext />
</BranchSelector>
</Branch>
);
}Features:
- Navigate between different conversation branches
- Automatic branch detection
- Keyboard navigation support
ChainOfThought
Step-by-step reasoning UI.
import {
ChainOfThought,
ChainOfThoughtContent,
ChainOfThoughtHeader,
ChainOfThoughtImage,
ChainOfThoughtSearchResult,
ChainOfThoughtSearchResults,
ChainOfThoughtStep,
} from "@/components/ai-elements/chain-of-thought";
export function ReasoningDisplay({ steps }) {
return (
<ChainOfThought>
<ChainOfThoughtHeader>Analysis Process</ChainOfThoughtHeader>
<ChainOfThoughtContent>
{steps.map((step, index) => (
<ChainOfThoughtStep
key={index}
label={step.label}
description={step.description}
status={step.status}
icon={step.icon}
>
{step.content}
</ChainOfThoughtStep>
))}
<ChainOfThoughtSearchResults>
<ChainOfThoughtSearchResult>
Search Result 1
</ChainOfThoughtSearchResult>
<ChainOfThoughtSearchResult>
Search Result 2
</ChainOfThoughtSearchResult>
</ChainOfThoughtSearchResults>
<ChainOfThoughtImage caption="Analysis visualization">
<img src="/analysis-chart.png" alt="Analysis" />
</ChainOfThoughtImage>
</ChainOfThoughtContent>
</ChainOfThought>
);
}Reasoning
Collapsible thinking block. Opens while streaming, then can collapse.
import {
Reasoning,
ReasoningContent,
ReasoningTrigger,
} from "@/components/ai-elements/reasoning";
export function AIReasoning({ isStreaming, reasoning }) {
return (
<Reasoning isStreaming={isStreaming} defaultOpen={true}>
<ReasoningTrigger />
<ReasoningContent>{reasoning}</ReasoningContent>
</Reasoning>
);
}Features:
- Auto-opens during streaming
- Auto-closes after completion
- Duration tracking
- Collapsible interface
Example
Chat shell with useChat:
"use client";
import { useChat } from "@ai-sdk/react";
import {
Conversation,
ConversationContent,
ConversationEmptyState,
ConversationScrollButton,
Loader,
Message,
MessageAvatar,
MessageContent,
PromptInput,
PromptInputBody,
PromptInputFooter,
PromptInputSubmit,
PromptInputTextarea,
Response,
} from "@/components/ai-elements";
export function ChatInterface() {
const { messages, sendMessage, status } = useChat();
return (
<div className="flex h-screen flex-col">
<Conversation>
<ConversationContent>
{messages.length === 0 ? (
<ConversationEmptyState
title="Start a conversation"
description="Ask me anything!"
/>
) : (
messages.map((message) => (
<Message key={message.id} from={message.role}>
<MessageAvatar
src={
message.role === "user"
? "/user-avatar.jpg"
: "/ai-avatar.jpg"
}
name={message.role === "user" ? "You" : "AI"}
/>
<MessageContent>
{message.parts.map((part, index) =>
part.type === "text" ? (
<Response key={index}>{part.text}</Response>
) : null
)}
</MessageContent>
</Message>
))
)}
{status === "submitted" && (
<Message from="assistant">
<MessageAvatar src="/ai-avatar.jpg" name="AI" />
<MessageContent>
<Loader />
</MessageContent>
</Message>
)}
</ConversationContent>
<ConversationScrollButton />
</Conversation>
<PromptInput
onSubmit={(message) => {
if (message.text.trim()) {
sendMessage({ text: message.text });
}
}}
>
<PromptInputBody>
<PromptInputTextarea placeholder="What would you like to know?" />
</PromptInputBody>
<PromptInputFooter>
<PromptInputSubmit status={status} />
</PromptInputFooter>
</PromptInput>
</div>
);
}Point useChat at your route with a transport when you are not using the default /api/chat:
"use client";
import { DefaultChatTransport } from "ai";
import { useChat } from "@ai-sdk/react";
const { messages, sendMessage, status } = useChat({
transport: new DefaultChatTransport({ api: "/api/chat" }),
});Theming
AI Elements use the same CSS variables as shadcn/ui. See Theming.
Next
ai sdk blocks.