# AI Elements
AI Elements is a React component library built on top of shadcn/ui designed specifically for building AI-native applications. It provides pre-built, customizable components for conversations, messages, code blocks, reasoning displays, tool execution, and more. The library integrates seamlessly with the Vercel AI SDK and follows the composable component pattern established by shadcn/ui.
The project includes a CLI tool for easy installation, a comprehensive set of React components organized into categories (chatbot, code, voice, workflow, utilities), and full TypeScript support. Components are designed to be copied into your project for maximum customization while maintaining consistent styling through Tailwind CSS and CSS Variables mode.
## Installation
### CLI Installation
Install all components or specific ones using the AI Elements CLI.
```bash
# Install all components
npx ai-elements@latest
# Install a specific component
npx ai-elements@latest add message
# Install multiple components
npx ai-elements@latest add conversation prompt-input code-block
# Alternative: Using shadcn CLI directly
npx shadcn@latest add https://elements.ai-sdk.dev/api/registry/all.json
# Install specific component via shadcn
npx shadcn@latest add https://elements.ai-sdk.dev/api/registry/message.json
```
## Conversation Component
The Conversation component wraps messages and provides automatic scrolling, empty states, and download functionality.
```tsx
"use client";
import { useChat } from "@ai-sdk/react";
import {
Conversation,
ConversationContent,
ConversationDownload,
ConversationEmptyState,
ConversationScrollButton,
} from "@/components/ai-elements/conversation";
import {
Message,
MessageContent,
MessageResponse,
} from "@/components/ai-elements/message";
import { MessageSquare } from "lucide-react";
export default function ChatPage() {
const { messages, sendMessage, status } = useChat();
return (
{messages.length === 0 ? (
}
title="Start a conversation"
description="Type a message below to begin chatting"
/>
) : (
messages.map((message) => (
{message.parts.map((part, i) => {
if (part.type === "text") {
return (
{part.text}
);
}
return null;
})}
))
)}
);
}
```
## Message Component
The Message component displays user and assistant messages with support for branching, actions, and markdown rendering via Streamdown.
```tsx
"use client";
import {
Message,
MessageContent,
MessageResponse,
MessageActions,
MessageAction,
MessageBranch,
MessageBranchContent,
MessageBranchSelector,
MessageBranchPrevious,
MessageBranchPage,
MessageBranchNext,
MessageToolbar,
} from "@/components/ai-elements/message";
import { CopyIcon, ThumbsUpIcon, ThumbsDownIcon, RefreshCwIcon } from "lucide-react";
// Basic message usage
export function BasicMessage() {
return (
{`# Hello World
Here's some **markdown** content with code:
\`\`\`javascript
const greeting = "Hello, AI Elements!";
console.log(greeting);
\`\`\`
`}
navigator.clipboard.writeText("content")}>
);
}
// Message with branching (multiple versions)
export function BranchedMessage({ versions }: { versions: { id: string; content: string }[] }) {
return (
console.log("Branch:", index)}>
{versions.map((version) => (
{version.content}
))}
{versions.length > 1 && (
)}
);
}
```
## PromptInput Component
The PromptInput component provides a rich text input with file attachments, action menus, model selection, and submit handling.
```tsx
"use client";
import { useState, useCallback } from "react";
import {
PromptInput,
PromptInputProvider,
PromptInputTextarea,
PromptInputBody,
PromptInputFooter,
PromptInputTools,
PromptInputSubmit,
PromptInputButton,
PromptInputActionMenu,
PromptInputActionMenuTrigger,
PromptInputActionMenuContent,
PromptInputActionAddAttachments,
PromptInputActionAddScreenshot,
usePromptInputAttachments,
type PromptInputMessage,
} from "@/components/ai-elements/prompt-input";
import {
Attachments,
Attachment,
AttachmentPreview,
AttachmentRemove,
} from "@/components/ai-elements/attachments";
import { GlobeIcon } from "lucide-react";
// Display attachments within PromptInput
function AttachmentsDisplay() {
const attachments = usePromptInputAttachments();
if (attachments.files.length === 0) return null;
return (
{attachments.files.map((file) => (
attachments.remove(file.id)}
>
))}
);
}
export function ChatInput() {
const [status, setStatus] = useState<"ready" | "submitted" | "streaming">("ready");
const handleSubmit = useCallback(async (message: PromptInputMessage) => {
if (!message.text && !message.files?.length) return;
setStatus("submitted");
console.log("Sending:", message.text, "Files:", message.files?.length ?? 0);
// Simulate API call
setTimeout(() => setStatus("streaming"), 200);
setTimeout(() => setStatus("ready"), 2000);
}, []);
const handleStop = useCallback(() => {
setStatus("ready");
console.log("Generation stopped");
}, []);
return (
console.error(err.code, err.message)}
>
Search
);
}
```
## CodeBlock Component
The CodeBlock component provides syntax-highlighted code display with Shiki, copy functionality, and language selection.
```tsx
"use client";
import { useState, useCallback } from "react";
import {
CodeBlock,
CodeBlockHeader,
CodeBlockTitle,
CodeBlockFilename,
CodeBlockActions,
CodeBlockCopyButton,
CodeBlockLanguageSelector,
CodeBlockLanguageSelectorTrigger,
CodeBlockLanguageSelectorValue,
CodeBlockLanguageSelectorContent,
CodeBlockLanguageSelectorItem,
} from "@/components/ai-elements/code-block";
import { FileIcon } from "lucide-react";
import type { BundledLanguage } from "shiki";
const codeExamples = {
typescript: {
code: `interface User {
id: string;
name: string;
email: string;
}
async function fetchUser(id: string): Promise {
const response = await fetch(\`/api/users/\${id}\`);
if (!response.ok) throw new Error("User not found");
return response.json();
}`,
filename: "user.ts",
},
python: {
code: `from dataclasses import dataclass
from typing import Optional
@dataclass
class User:
id: str
name: str
email: str
async def fetch_user(user_id: str) -> Optional[User]:
response = await client.get(f"/api/users/{user_id}")
return User(**response.json()) if response.ok else None`,
filename: "user.py",
},
};
export function CodeBlockExample() {
const [language, setLanguage] = useState<"typescript" | "python">("typescript");
const { code, filename } = codeExamples[language];
return (
{filename}TypeScriptPython console.log("Copied!")}
onError={(err) => console.error("Copy failed:", err)}
/>
);
}
```
## Reasoning Component
The Reasoning component displays AI thinking/reasoning content with auto-expand/collapse behavior during streaming.
```tsx
"use client";
import { useState, useEffect } from "react";
import {
Reasoning,
ReasoningTrigger,
ReasoningContent,
} from "@/components/ai-elements/reasoning";
export function ReasoningExample() {
const [content, setContent] = useState("");
const [isStreaming, setIsStreaming] = useState(true);
useEffect(() => {
// Simulate streaming reasoning content
const steps = [
"Let me analyze this problem step by step.\n\n",
"First, I need to understand the requirements...\n\n",
"The solution involves three main components:\n",
"1. Data validation\n",
"2. Processing logic\n",
"3. Output formatting",
];
let currentText = "";
let stepIndex = 0;
const interval = setInterval(() => {
if (stepIndex < steps.length) {
currentText += steps[stepIndex];
setContent(currentText);
stepIndex++;
} else {
setIsStreaming(false);
clearInterval(interval);
}
}, 500);
return () => clearInterval(interval);
}, []);
return (
streaming ? "Thinking..." : `Thought for ${duration} seconds`
}
/>
{content}
);
}
// Controlled reasoning component
export function ControlledReasoning({
reasoning
}: {
reasoning: { content: string; duration: number }
}) {
const [isOpen, setIsOpen] = useState(false);
return (
{reasoning.content}
);
}
```
## Tool Component
The Tool component displays AI tool invocations with their parameters, status, and results.
```tsx
"use client";
import {
Tool,
ToolHeader,
ToolContent,
ToolInput,
ToolOutput,
getStatusBadge,
} from "@/components/ai-elements/tool";
import type { ToolUIPart } from "ai";
interface ToolCallProps {
name: string;
state: ToolUIPart["state"];
input: Record;
output?: unknown;
errorText?: string;
}
export function ToolCall({ name, state, input, output, errorText }: ToolCallProps) {
return (
);
}
// Example usage with search tool
export function SearchToolExample() {
return (
);
}
// Tool with error state
export function FailedToolExample() {
return (
);
}
```
## Sources Component
The Sources component displays citation sources used by the AI in generating responses.
```tsx
"use client";
import {
Sources,
SourcesTrigger,
SourcesContent,
Source,
} from "@/components/ai-elements/sources";
interface SourceData {
href: string;
title: string;
}
export function SourcesExample({ sources }: { sources: SourceData[] }) {
if (sources.length === 0) return null;
return (
{sources.map((source) => (
))}
);
}
// Example with React documentation sources
export function DocumentationSources() {
const sources = [
{ href: "https://react.dev/reference/react", title: "React Documentation" },
{ href: "https://react.dev/reference/react-dom", title: "React DOM Reference" },
{ href: "https://react.dev/learn", title: "React Tutorial" },
];
return ;
}
```
## Suggestion Component
The Suggestion component displays clickable prompt suggestions for users.
```tsx
"use client";
import { useCallback } from "react";
import { Suggestions, Suggestion } from "@/components/ai-elements/suggestion";
const prompts = [
"What are the latest trends in AI?",
"How does machine learning work?",
"Explain quantum computing",
"Best practices for React development",
];
export function SuggestionsList({ onSelect }: { onSelect: (text: string) => void }) {
const handleClick = useCallback((suggestion: string) => {
onSelect(suggestion);
}, [onSelect]);
return (
{prompts.map((prompt) => (
))}
);
}
```
## ModelSelector Component
The ModelSelector component provides a searchable dialog for selecting AI models from various providers.
```tsx
"use client";
import { useState, useCallback } from "react";
import {
ModelSelector,
ModelSelectorTrigger,
ModelSelectorContent,
ModelSelectorInput,
ModelSelectorList,
ModelSelectorEmpty,
ModelSelectorGroup,
ModelSelectorItem,
ModelSelectorLogo,
ModelSelectorLogoGroup,
ModelSelectorName,
} from "@/components/ai-elements/model-selector";
import { Button } from "@/components/ui/button";
import { CheckIcon } from "lucide-react";
const models = [
{ id: "gpt-4o", name: "GPT-4o", provider: "openai", providers: ["openai", "azure"] },
{ id: "claude-sonnet-4", name: "Claude 4 Sonnet", provider: "anthropic", providers: ["anthropic", "google"] },
{ id: "gemini-2.0-flash", name: "Gemini 2.0 Flash", provider: "google", providers: ["google"] },
];
export function ModelSelectorExample() {
const [selectedModel, setSelectedModel] = useState(models[0].id);
const [open, setOpen] = useState(false);
const selected = models.find((m) => m.id === selectedModel);
const handleSelect = useCallback((modelId: string) => {
setSelectedModel(modelId);
setOpen(false);
}, []);
return (
No models found.
{["openai", "anthropic", "google"].map((provider) => (
{models.filter((m) => m.provider === provider).map((model) => (
handleSelect(model.id)}
>
{model.name}
{model.providers.map((p) => (
))}
{selectedModel === model.id && }
))}
))}
);
}
```
## FileTree Component
The FileTree component displays hierarchical file and folder structures with expand/collapse and selection support.
```tsx
"use client";
import { useState, useCallback } from "react";
import {
FileTree,
FileTreeFolder,
FileTreeFile,
FileTreeIcon,
FileTreeName,
} from "@/components/ai-elements/file-tree";
import { FileCodeIcon, FileJsonIcon } from "lucide-react";
export function FileTreeExample() {
const [selectedPath, setSelectedPath] = useState();
const [expanded, setExpanded] = useState(new Set(["src", "src/components"]));
const handleSelect = useCallback((path: string) => {
setSelectedPath(path);
console.log("Selected:", path);
}, []);
return (
}
/>
}
/>
}
/>
);
}
```
## Attachments Component
The Attachments component displays file attachments with preview, removal, and multiple layout variants.
```tsx
"use client";
import {
Attachments,
Attachment,
AttachmentPreview,
AttachmentInfo,
AttachmentRemove,
AttachmentHoverCard,
AttachmentHoverCardTrigger,
AttachmentHoverCardContent,
getMediaCategory,
} from "@/components/ai-elements/attachments";
import type { FileUIPart } from "ai";
interface AttachmentFile extends FileUIPart {
id: string;
}
export function AttachmentsGrid({
files,
onRemove
}: {
files: AttachmentFile[];
onRemove: (id: string) => void;
}) {
return (
{files.map((file) => (
onRemove(file.id)}>
))}
);
}
export function AttachmentsList({
files,
onRemove
}: {
files: AttachmentFile[];
onRemove: (id: string) => void;
}) {
return (
{files.map((file) => (
onRemove(file.id)}>
))}
);
}
// Inline variant with hover preview
export function AttachmentsInline({ files }: { files: AttachmentFile[] }) {
return (
{files.map((file) => (
))}
);
}
```
## Backend API Route Example
Create a Next.js API route to handle chat requests with the AI SDK.
```tsx
// app/api/chat/route.ts
import { streamText, UIMessage, convertToModelMessages } from "ai";
import { openai } from "@ai-sdk/openai";
export const maxDuration = 30;
export async function POST(req: Request) {
const { messages }: { messages: UIMessage[] } = await req.json();
const result = streamText({
model: openai("gpt-4o"),
messages: await convertToModelMessages(messages),
system: "You are a helpful assistant.",
});
return result.toUIMessageStreamResponse();
}
```
## Complete Chat Application Example
A full-featured chat application combining multiple AI Elements components.
```tsx
"use client";
import { useChat } from "@ai-sdk/react";
import { Conversation, ConversationContent, ConversationScrollButton } from "@/components/ai-elements/conversation";
import { Message, MessageContent, MessageResponse } from "@/components/ai-elements/message";
import { Reasoning, ReasoningTrigger, ReasoningContent } from "@/components/ai-elements/reasoning";
import { Sources, SourcesTrigger, SourcesContent, Source } from "@/components/ai-elements/sources";
import { Tool, ToolHeader, ToolContent, ToolInput, ToolOutput } from "@/components/ai-elements/tool";
import { PromptInput, PromptInputTextarea, PromptInputFooter, PromptInputSubmit, type PromptInputMessage } from "@/components/ai-elements/prompt-input";
import { Suggestions, Suggestion } from "@/components/ai-elements/suggestion";
export default function ChatApp() {
const { messages, append, status, stop } = useChat();
const handleSubmit = async (message: PromptInputMessage) => {
if (!message.text.trim()) return;
await append({ role: "user", content: message.text });
};
const handleSuggestion = (text: string) => {
append({ role: "user", content: text });
};
return (
{messages.map((msg) => (
{msg.parts.map((part, i) => {
switch (part.type) {
case "reasoning":
return (
{part.reasoning}
);
case "source":
return (
);
case "tool-invocation":
return (
);
case "text":
return {part.text};
default:
return null;
}
})}
))}
{messages.length === 0 && (
{["Explain React hooks", "Write a TypeScript function", "Debug my code"].map((s) => (
))}
)}
);
}
```
## Summary
AI Elements provides a comprehensive set of React components for building AI-powered chat interfaces, code editors, and workflow visualizations. The library's composable architecture allows developers to mix and match components to create custom experiences while maintaining consistent styling through Tailwind CSS. Key integration patterns include using the Vercel AI SDK's `useChat` hook for state management, streaming responses with `streamText`, and handling tool invocations through the Tool component.
The library excels at rapid prototyping of AI applications while remaining flexible enough for production use. Components like Conversation, Message, and PromptInput handle common chat UI patterns, while specialized components like CodeBlock, Reasoning, and Tool display AI-specific content. The CLI tool simplifies installation by automatically adding components to your shadcn/ui setup, and since all components are copied into your project, you have full control over customization without dependency lock-in.