# Agent Elements: Full docs Source: https://agent-elements.21st.dev/llms-full.txt --- # Introduction URL: https://agent-elements.21st.dev/docs Agent Elements is an open-source collection of chat and agent UI components (messages, tool cards, streaming states, and input controls), built on top of shadcn/ui. Copy them into your project with one command and own the source. The API is typed around `UIMessage` and `ChatStatus` from the Vercel AI SDK, so `useChat` plugs in directly. Agent Elements also serves as the UI layer of the 21st.dev Agent SDK — if you want a full Claude Code or Codex agent with a sandbox and this UI preconfigured, use the SDK directly at https://21st.dev/agents. ## Component groups - **Chat surface**: AgentChat, MessageList, UserMessage, ErrorMessage, Markdown - **Input**: InputBar, Suggestions, ModelPicker, ModeSelector, SendButton, AttachmentButton, FileAttachment - **Tool cards**: BashTool, EditTool, SearchTool, TodoTool, PlanTool, ToolGroup, SubagentTool, McpTool, QuestionTool, GenericTool - **Streaming states**: ThinkingTool, TextShimmer, SpiralLoader ## Quick start ```bash npx shadcn@latest add https://agent-elements.21st.dev/r/agent-chat.json ``` --- # Installation URL: https://agent-elements.21st.dev/docs/installation ## Prerequisites - Node 18+ - React 19+ - Tailwind CSS v4 (`@import "tailwindcss"` in your global stylesheet) - A shadcn-initialised project ## Initialise shadcn ```bash npx shadcn@latest init ``` This scaffolds `components.json`, configures the `@/*` path alias, and adds a `cn` helper. Agent Elements uses all of them. ## Add a component Every Agent Elements component is its own registry item. Installing one pulls in every piece it needs via `registryDependencies`. ```bash npx shadcn@latest add https://agent-elements.21st.dev/r/agent-chat.json ``` ## Usage ```tsx "use client"; import { AgentChat } from "@/components/agent-elements/agent-chat"; import type { UIMessage } from "ai"; const messages: UIMessage[] = [ { id: "msg-1", role: "assistant", parts: [{ type: "text", text: "Welcome to Agent Elements." }], }, ]; export default function App() { return ( {}} onStop={() => {}} /> ); } ``` --- # MCP URL: https://agent-elements.21st.dev/docs/mcp The Agent Elements registry is compatible with the shadcn Model Context Protocol server. Point any MCP client (Cursor, Claude Code, Windsurf) at the registry URL and your assistant can browse, preview, and install components without leaving the editor. ## Cursor setup Add to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (per project): ```json { "mcpServers": { "agent-elements": { "command": "npx", "args": ["-y", "shadcn@canary", "mcp"], "env": { "REGISTRY_URL": "https://agent-elements.21st.dev/r/index.json" } } } } ``` ## Claude Code setup ```bash claude mcp add agent-elements -- npx -y shadcn@canary mcp --env REGISTRY_URL=https://agent-elements.21st.dev/r/index.json ``` ## Example prompts - Show me all available Agent Elements components. - Add the AgentChat component to my project and wire it to the Vercel AI SDK. - Show me the API reference and a preview of InputBar. - Build a chat app using AgentChat with BashTool and EditTool renderers. --- # Skills URL: https://agent-elements.21st.dev/docs/skills Skills give AI assistants like Claude Code and Cursor project-aware context about Agent Elements. When installed, your assistant knows how to find, install, compose, and customise components using the correct APIs, prop shapes, and styling patterns. ## Install ```bash npx skills add 21st-dev/agent-elements ``` ## What's included - Project detection (reads `components.json`) - Component catalog with API shapes and prop defaults - Composition patterns (AgentChat + tool renderers, InputBar + AI SDK, etc.) - Theming guardrails - Registry access for on-demand installs --- # Components ## AgentChat URL: https://agent-elements.21st.dev/docs/agent-chat Install: `npx shadcn@latest add https://agent-elements.21st.dev/r/agent-chat.json` ### Code ```tsx import { AgentChat } from "@/components/agent-elements/agent-chat"; const messages = [ { id: "msg-1", role: "assistant", parts: [{ type: "text", text: "Welcome to Agent Elements." }], }, ]; const promptSuggestions = [ { id: "write", label: "Write", value: "Write release notes for this change." }, { id: "plan", label: "Plan", value: "Create a rollout plan in 5 steps." }, ]; export function Example() { return (
{}} onStop={() => {}} showCopyToolbar emptyStatePosition="center" emptySuggestionsPlacement="empty" emptySuggestionsPosition="bottom" suggestions={{ items: promptSuggestions }} />
); } ``` ### API Reference ```ts type AgentChatProps = { messages: UIMessage[]; onSend: (message: { role: "user"; content: string }) => void; status: ChatStatus; onStop: () => void; error?: Error; classNames?: Partial<{ root: string; inputBar: string; userMessage: string; }>; slots?: Partial<{ InputBar: React.ComponentType; UserMessage: React.ComponentType; ToolRenderer: React.ComponentType; }>; toolRenderers?: Record>; showCopyToolbar?: boolean; attachments?: { onAttach?: () => void; images?: { id: string; filename: string; url: string; size?: number }[]; files?: { id: string; filename: string; size?: number }[]; onRemoveImage?: (id: string) => void; onRemoveFile?: (id: string) => void; onPaste?: (e: React.ClipboardEvent) => void; isDragOver?: boolean; }; suggestions?: | SuggestionItem[] | { items: SuggestionItem[]; className?: string; itemClassName?: string }; emptyStatePosition?: "default" | "center"; emptySuggestionsPlacement?: "input" | "empty" | "both"; emptySuggestionsPosition?: "top" | "bottom"; questionTool?: { submitLabel?: string; skipLabel?: string; allowSkip?: boolean; onAnswer?: (payload: { toolCallId?: string; question: QuestionConfig; answer: QuestionAnswer; }) => void; }; className?: string; style?: React.CSSProperties; }; ``` ### Usage Create a full chat surface with messages, status, and send/stop handlers. Add `attachments` to wire file/image context, `questionTool` to handle Question tool answers, and `showCopyToolbar` for text copy actions. Use `emptyStatePosition`, `emptySuggestionsPlacement`, and `emptySuggestionsPosition` to shape empty-state behavior. Get started at [21st.dev/agents/docs/get-started](https://21st.dev/agents/docs/get-started). ### Example: Basic ```tsx {}} onStop={() => {}} /> ``` ### Example: Empty centered ```tsx {}} onStop={() => {}} emptyStatePosition="center" /> ``` ### Example: Empty centered + suggestions ```tsx {}} onStop={() => {}} emptyStatePosition="center" emptySuggestionsPlacement="empty" emptySuggestionsPosition="bottom" suggestions={{ items: promptSuggestions }} /> ``` ### Example: With attachments ```tsx {}} onStop={() => {}} attachments={{ onAttach: () => {}, images: [{ id: "img-1", filename: "preview.png", url: imageUrl }], files: [{ id: "file-1", filename: "spec.md", size: 3200 }], onRemoveImage: () => {}, onRemoveFile: () => {}, }} /> ``` ### Example: Copy toolbar ```tsx {}} onStop={() => {}} showCopyToolbar /> ``` --- ## MessageList URL: https://agent-elements.21st.dev/docs/message-list Install: `npx shadcn@latest add https://agent-elements.21st.dev/r/message-list.json` ### Code ```tsx import { MessageList } from "@/components/agent-elements/message-list"; import type { UIMessage } from "ai"; const messages: UIMessage[] = [ { id: "msg-1", role: "user", parts: [{ type: "text", text: "Share the latest status." }], createdAt: new Date(), }, { id: "msg-2", role: "assistant", parts: [{ type: "text", text: "All systems are green." }], }, ]; export function Example() { return ; } ``` ### API Reference ```ts type MessageListProps = { messages: UIMessage[]; status: ChatStatus; className?: string; showCopyToolbar?: boolean; slots?: { UserMessage?: React.ComponentType<{ message: UIMessage; className?: string }>; ToolRenderer?: React.ComponentType; }; classNames?: { userMessage?: string; }; toolRenderers?: Record>; }; ``` ### Usage Render the full transcript from UIMessage[]. Use showCopyToolbar for user/assistant text copy, className for container sizing, and slots/classNames/toolRenderers for custom rendering. ### Example: Basic transcript ```tsx const messages: UIMessage[] = [ { id: "msg-1", role: "user", parts: [{ type: "text", text: "Share the latest status." }], }, { id: "msg-2", role: "assistant", parts: [{ type: "text", text: "All systems are green." }], }, { id: "msg-3", role: "user", parts: [{ type: "text", text: "Any regressions from last deploy?" }], }, { id: "msg-4", role: "assistant", parts: [{ type: "text", text: "No new errors in the last 24 hours." }], }, { id: "msg-5", role: "user", parts: [{ type: "text", text: "Summarize open tickets." }], }, { id: "msg-6", role: "assistant", parts: [ { type: "text", text: "3 open: billing follow-up, onboarding issue, and API timeout investigation.", }, ], }, ]; ``` ### Example: With timestamps ```tsx const messages: UIMessage[] = [ { id: "msg-1", role: "user", parts: [{ type: "text", text: "Can you summarize this?" }], createdAt: new Date(), }, { id: "msg-2", role: "assistant", parts: [{ type: "text", text: "Here is the summary." }], }, ]; ``` --- ## InputBar URL: https://agent-elements.21st.dev/docs/input-bar Install: `npx shadcn@latest add https://agent-elements.21st.dev/r/input-bar.json` ### Code ```tsx import { InputBar } from "@/components/agent-elements/input-bar"; export function Example() { return ( console.log(content)} status="ready" onStop={() => {}} /> ); } ``` ### API Reference ```ts type InputBarProps = { onSend: (message: { role: "user"; content: string }) => void; status: ChatStatus; onStop: () => void; placeholder?: string; className?: string; disabled?: boolean; autoFocus?: boolean; value?: string; onChange?: (value: string) => void; onAttach?: () => void; attachedImages?: AttachedImage[]; attachedFiles?: AttachedFile[]; onRemoveImage?: (id: string) => void; onRemoveFile?: (id: string) => void; onPaste?: (e: React.ClipboardEvent) => void; isDragOver?: boolean; suggestions?: InputSuggestions; typingAnimation?: { text: string; duration: number; image?: string; isActive: boolean; onComplete: () => void; }; infoBar?: { title?: string; description?: string; onClose?: () => void; position?: "top" | "bottom"; }; questionBar?: { id: string; questions: QuestionConfig[]; questionIndex?: number; totalQuestions?: number; onPreviousQuestion?: () => void; onNextQuestion?: () => void; submitLabel?: string; skipLabel?: string; allowSkip?: boolean; onSubmit: (answer: QuestionAnswer) => void; onSkip?: () => void; }; // Toolbar composition slots. Drop any ReactNode (model picker, mode selector, custom toggles, …) leftActions?: React.ReactNode; rightActions?: React.ReactNode; }; ``` ### Usage Collect prompts and attachments in the composer. Supports controlled mode (value/onChange), drag/paste handling, info bar, typing animation, multi-question navigation, and free-form toolbar slots (leftActions/rightActions) for composing model/mode pickers or any custom controls. ### Example: Basic input ```tsx ``` ### Example: With attachments ```tsx ``` ### Example: Focus outline ```tsx
``` ### Example: Info bar ```tsx {}, }} /> ``` ### Example: Info bar (bottom) ```tsx {}, position: "bottom", }} /> ``` ### Example: Question bar ```tsx const questions = [ { kind: "single", title: "Which direction should I take?", options: [ { id: "small", label: "Small patch" }, { id: "full", label: "Full refactor" }, ], allowCustom: true, }, { kind: "single", title: "How cautious should the rollout be?", options: [ { id: "safe", label: "Safe and incremental" }, { id: "fast", label: "Fast rollout" }, ], allowCustom: true, }, ]; console.log(answer), }} /> ``` ### Example: Toolbar actions (model + mode) ```tsx import { InputBar } from "@/components/agent-elements/input-bar"; import { ModelPicker } from "@/components/agent-elements/input/model-picker"; import { ModeSelector } from "@/components/agent-elements/input/mode-selector"; import { IconCursor, IconBulb } from "@tabler/icons-react"; const models = [ { id: "sonnet", name: "Sonnet", version: "4.6" }, { id: "opus", name: "Opus", version: "4.7" }, ]; const modes = [ { id: "agent", label: "Agent", icon: IconCursor }, { id: "plan", label: "Plan", icon: IconBulb }, ]; } /> ``` --- ## Suggestions URL: https://agent-elements.21st.dev/docs/suggestions Install: `npx shadcn@latest add https://agent-elements.21st.dev/r/suggestions.json` ### Code ```tsx import { InputBar } from "@/components/agent-elements/input-bar"; import { IconCalendar, IconCode, IconPencil, IconSearch } from "@tabler/icons-react"; import { useState } from "react"; const items = [ { id: "write", label: "Write", value: "Write a concise project update with key milestones.", icon: , }, { id: "learn", label: "Learn", value: "Explain this codebase architecture in plain language.", icon: , }, { id: "code", label: "Code", value: "Generate a clean starter implementation for this feature.", icon: , }, { id: "calendar", label: "From Calendar", value: "Draft my agenda from tomorrow's calendar events.", icon: , }, ]; export function Example() { const [value, setValue] = useState(""); return ( console.log(content)} status="ready" onStop={() => {}} suggestions={{ items, className: "justify-center", itemClassName: "h-7 rounded-[6px] px-2 text-sm", }} /> ); } ``` ### API Reference ```ts type SuggestionItem = { id: string; label: string; value?: string; icon?: ReactNode; className?: string; }; type SuggestionsProps = { items: SuggestionItem[]; onSelect: (item: SuggestionItem) => void; disabled?: boolean; className?: string; itemClassName?: string; }; ``` ### Usage Show quick prompt chips and write the selected suggestion into InputBar for fast message drafting. Use disabled to pause interaction and item.className for per-chip styling. ### Example: Icons + text ```tsx import { IconCalendar, IconCode, IconPencil, IconSearch } from "@tabler/icons-react"; const items = [ { id: "write", label: "Write", icon: }, { id: "learn", label: "Learn", icon: }, { id: "code", label: "Code", icon: }, { id: "calendar", label: "From Calendar", icon: }, ]; console.log(item)} className="justify-center" itemClassName="h-7 rounded-[6px] px-2 text-sm" /> ``` ### Example: Fill InputBar ```tsx const [value, setValue] = useState(""); ``` --- ## ModelPicker URL: https://agent-elements.21st.dev/docs/model-picker Install: `npx shadcn@latest add https://agent-elements.21st.dev/r/model-picker.json` ### Code ```tsx import { ModelPicker } from "@/components/agent-elements/input/model-picker"; import { useState } from "react"; const models = [ { id: "sonnet", name: "Sonnet", version: "4.6" }, { id: "opus", name: "Opus", version: "4.7" }, { id: "haiku", name: "Haiku", version: "4.5" }, ]; export function Example() { const [model, setModel] = useState("sonnet"); return ( ); } ``` ### API Reference ```ts type ModelOption = { id: string; name: string; version?: string; }; type ModelPickerProps = { models: ModelOption[]; value?: string; // controlled defaultValue?: string; // uncontrolled onChange?: (modelId: string) => void; placeholder?: string; // shown when no model matches, default "Auto" className?: string; }; type ModelBadgeProps = { models: ModelOption[]; value?: string; placeholder?: string; className?: string; }; ``` ### Usage Standalone model picker. Drop it into InputBar via leftActions/rightActions, a header, a settings sheet, or anywhere else. It does not depend on InputBar. Supports controlled and uncontrolled modes. Use ModelBadge for a read-only variant. ### Example: Uncontrolled ```tsx ``` ### Example: Inside InputBar ```tsx } /> ``` ### Example: Read-only badge ```tsx ``` --- ## ModeSelector URL: https://agent-elements.21st.dev/docs/mode-selector Install: `npx shadcn@latest add https://agent-elements.21st.dev/r/mode-selector.json` ### Code ```tsx import { ModeSelector, type ModeOption } from "@/components/agent-elements/input/mode-selector"; import { IconCursor, IconBulb } from "@tabler/icons-react"; import { useState } from "react"; const modes: ModeOption[] = [ { id: "agent", label: "Agent", icon: IconCursor }, { id: "plan", label: "Plan", icon: IconBulb, description: "Think before acting" }, ]; export function Example() { const [mode, setMode] = useState("agent"); return ; } ``` ### API Reference ```ts type ModeOption = { id: string; label: string; icon?: React.ComponentType<{ className?: string }>; description?: string; }; type ModeSelectorProps = { modes: ModeOption[]; value?: string; // controlled defaultValue?: string; // uncontrolled onChange?: (modeId: string) => void; className?: string; }; ``` ### Usage Standalone mode selector: agent mode, plan mode, or any custom set. Bring your own icons or omit them. With a single mode the selector renders a non-interactive label. ### Example: Uncontrolled ```tsx ``` ### Example: Inside InputBar ```tsx } /> ``` --- ## UserMessage URL: https://agent-elements.21st.dev/docs/user-message Install: `npx shadcn@latest add https://agent-elements.21st.dev/r/user-message.json` ### Code ```tsx import { UserMessage } from "@/components/agent-elements/user-message"; import type { UIMessage } from "ai"; const message: UIMessage = { id: "user-1", role: "user", parts: [{ type: "text", text: "Share the latest status." }], }; export function Example() { return ; } ``` ### Usage Render a single user bubble. Supports text, image parts (image/data-image/image file), and file attachments. ### Example: Text only ```tsx const message: UIMessage = { id: "user-1", role: "user", parts: [{ type: "text", text: "Share the latest status." }], }; ``` ### Example: With image ```tsx const message: UIMessage = { id: "user-2", role: "user", parts: [ { type: "text", text: "Here is the screenshot." }, { type: "data-image", data: { url: imageUrl } }, ], }; ``` --- ## Markdown URL: https://agent-elements.21st.dev/docs/markdown Install: `npx shadcn@latest add https://agent-elements.21st.dev/r/markdown.json` ### Code ```tsx import { Markdown } from "@/components/agent-elements/markdown"; const content = [ "# Release notes", "", "- Added streaming markdown", "- Improved tool rendering", "", "## Example", "", "Use \"Markdown\" to render assistant text as it streams.", "", "```ts", "console.log(\"Hello from markdown\");", "```", ].join(\"\n\"); export function Example() { return ; } ``` ### Usage Render streaming markdown with headings, lists, tables, blockquotes, and code fences. External links get safe target/rel handling. ### Example: Release note snippet ```tsx const content = [ "# Release notes", "", "- Added streaming markdown", "- Improved tool rendering", "", "## Example", "", "Use \"Markdown\" to render assistant text as it streams.", "", "```ts", "console.log(\"Hello from markdown\");", "```", ].join(\"\n\"); ``` ### Example: Tables + links ```tsx const content = [ "| Tool | Status |", "| --- | --- |", "| Search | Ready |", "| Bash | Ready |", "", "Visit [docs](https://example.com) for details.", ].join(\"\n\"); ``` ### Example: Streaming update ```tsx import { useEffect, useState } from "react"; const fullContent = [ "### Working plan", "", "- Parse input context", "- Extract constraints", "- Draft outline", "", "#### Draft", "We will deliver a tight summary, then provide supporting details.", "", "```ts", "const steps = [\"parse\", \"outline\", \"draft\"];", "```", "", "| Step | Status |", "| --- | --- |", "| Parse | Done |", "| Outline | Done |", "| Draft | Running |", "", "Final answer coming next...", ].join(\"\n\"); export function Example() { const [content, setContent] = useState(\"\"); const [isStreaming, setIsStreaming] = useState(false); const runStream = () => { setContent(\"\"); setIsStreaming(true); let i = 0; const tick = () => { i += 1; setContent(fullContent.slice(0, i)); if (i >= fullContent.length) { setIsStreaming(false); return; } setTimeout(tick, 18); }; setTimeout(tick, 120); }; useEffect(() => { runStream(); }, []); return (
{isStreaming ? \"Streaming...\" : \"Idle\"}
); } ``` --- ## AttachmentButton URL: https://agent-elements.21st.dev/docs/attachment-button Install: `npx shadcn@latest add https://agent-elements.21st.dev/r/attachment-button.json` ### Code ```tsx import { AttachmentButton } from "@/components/agent-elements/input/attachment-button"; export function Example() { return {}} />; } ``` ### Usage Render the round plus attachment trigger used by InputBar. Use onClick to open your picker action. ### Example: Default ```tsx {}} /> ``` ### Example: Paperclip icon ```tsx {}} icon="paperclip" /> ``` ### Example: Without handler ```tsx ``` --- ## SendButton URL: https://agent-elements.21st.dev/docs/send-button Install: `npx shadcn@latest add https://agent-elements.21st.dev/r/send-button.json` ### Code ```tsx import { SendButton } from "@/components/agent-elements/input/send-button"; export function Example() { return ( ); } ``` ### Usage Render the send/stop control. Use state=idle | typing | streaming. ### Example: Idle ```tsx ``` ### Example: Typing ```tsx ``` ### Example: Streaming ```tsx ``` --- ## FileAttachment URL: https://agent-elements.21st.dev/docs/file-attachment Install: `npx shadcn@latest add https://agent-elements.21st.dev/r/file-attachment.json` ### Code ```tsx import { FileAttachment } from "@/components/agent-elements/input/file-attachment"; export function Example() { return ( {}} /> ); } ``` ### Usage Render a file/image chip. Use isImage + url for thumbnails, display="image-only" for previews, and onRemove to show the close control. ### Example: File + image ```tsx ``` ### Example: Image only ```tsx {}} /> ``` ### Example: Removable file ```tsx {}} /> ``` --- ## TextShimmer URL: https://agent-elements.21st.dev/docs/text-shimmer Install: `npx shadcn@latest add https://agent-elements.21st.dev/r/text-shimmer.json` ### Code ```tsx import { TextShimmer } from "@/components/agent-elements/text-shimmer"; export function Example() { return ( Syncing metadata ); } ``` ### Usage Render shimmering status text. Tune duration, spread, and delay. ### Example: Inline status ```tsx Syncing metadata ``` ### Example: Delayed shimmer ```tsx Calculating risk score ``` ### Example: Fast shimmer ```tsx Rapid sync ``` --- ## SpiralLoader URL: https://agent-elements.21st.dev/docs/spiral-loader Install: `npx shadcn@latest add https://agent-elements.21st.dev/r/spiral-loader.json` ### Code ```tsx import { SpiralLoader } from "@/components/agent-elements/spiral-loader"; export function Example() { return ; } ``` ### Usage Render the spiral loader. Use size to control the square canvas and className for layout styling. ### Example: Sizes ```tsx
``` --- ## BashTool URL: https://agent-elements.21st.dev/docs/bash-tool Install: `npx shadcn@latest add https://agent-elements.21st.dev/r/bash-tool.json` ### Code ```tsx import { BashTool } from "@/components/agent-elements/tools/bash-tool"; const part = { type: "tool-Bash", toolCallId: "bash-1", state: "output-available", input: { command: "ls -la" }, output: { stdout: "app\nlib\nREADME.md" }, }; export function Example() { return ; } ``` ### Usage Render a command tool card. Provide input.command and optional output.stdout; use input.approval for the footer. ### Example: Terminal card ```tsx ``` ### Example: Running state ```tsx const pendingPart = { type: "tool-Bash", toolCallId: "bash-2", state: "input-streaming", input: { command: "git status" }, }; ``` ### Example: Approval footer ```tsx const approvalPart = { type: "tool-Bash", toolCallId: "bash-3", state: "input-available", input: { command: "pnpm test --filter ./apps/web -- --runInBand", approval: { approveLabel: "Run", rejectLabel: "Skip" }, }, }; ``` --- ## EditTool URL: https://agent-elements.21st.dev/docs/edit-tool Install: `npx shadcn@latest add https://agent-elements.21st.dev/r/edit-tool.json` ### Code ```tsx import { EditTool } from "@/components/agent-elements/tools/edit-tool"; const part = { type: "tool-Edit", toolCallId: "edit-1", state: "output-available", input: { file_path: "/app/page.tsx" }, output: { old_content: "export const metadata = { title: 'Old' };\n\nexport default function Page() {\n return
Old content
;\n}\n", content: "export const metadata = { title: 'Updated' };\n\nexport default function Page() {\n return (\n
\n

Release notes

\n

New layout applied.

\n
\n );\n}\n", }, }; export function Example() { return ; } ``` ### Usage Render a diff card for file edits. Supply input.file_path plus diff content (old/new or structuredPatch); use input.approval for the footer. ### Example: Diff card ```tsx ``` ### Example: Approval footer ```tsx const approvalPart = { type: "tool-Edit", toolCallId: "edit-7", state: "output-available", input: { file_path: "/app/page.tsx", approval: { approveLabel: "Apply", rejectLabel: "Skip" }, }, output: { old_content: "export const metadata = { title: 'Old' };\n\nexport default function Page() {\n return
Old content
;\n}\n", content: "export const metadata = { title: 'Updated' };\n\nexport default function Page() {\n return
New content
;\n}\n", }, }; ``` ### Example: Collapsible diff ```tsx const longPart = { type: "tool-Edit", toolCallId: "edit-1b", state: "output-available", input: { file_path: "/app/page.tsx" }, output: { old_content: "export const metadata = { title: 'Old' };\n\nexport default function Page() {\n return (\n
\n

Dashboard

\n

Old copy here.

\n
\n

Highlights

\n
    \n
  • Shipping ETA
  • \n
  • Billing status
  • \n
  • Support inbox
  • \n
\n
\n
\n

Activity

\n

Recent items...

\n
\n
\n );\n}\n", content: "export const metadata = { title: 'Updated' };\n\nexport default function Page() {\n return (\n
\n
\n

Release notes

\n

New layout applied.

\n
\n
\n

Highlights

\n
    \n
  • Sync latency improvements
  • \n
  • Workspace search redesign
  • \n
  • Billing transparency
  • \n
\n
\n
\n

Activity

\n

Recent items with timestamps...

\n
\n
\n

More

\n

Additional details and links.

\n
\n
\n );\n}\n", }, }; ``` ### Example: Pending edit ```tsx const pendingPart = { type: "tool-Edit", toolCallId: "edit-2", state: "input-streaming", input: { file_path: "/app/page.tsx", old_string: "const title = 'Old';\n", new_string: "const title = 'Updated';\n", }, }; ``` ### Example: Waiting for diff ```tsx const placeholderPart = { type: "tool-Edit", toolCallId: "edit-2b", state: "input-streaming", input: {}, }; ``` ### Example: Structured patch ```tsx const patchPart = { type: "tool-Edit", toolCallId: "edit-3", state: "output-available", input: { file_path: "/app/page.tsx" }, output: { structuredPatch: [ { lines: [ "-const title = 'Old';", "+const title = 'Updated';", ], }, ], }, }; ``` ### Example: Write tool ```tsx const writePart = { type: "tool-Write", toolCallId: "write-1", state: "output-available", input: { file_path: "/app/new.tsx" }, output: { content: "export const Demo = () => null\n" }, }; ``` ### Example: Missing file path ```tsx const noPathPart = { type: "tool-Edit", toolCallId: "edit-4", state: "output-available", input: { old_string: "foo", new_string: "bar" }, }; ``` --- ## SearchTool URL: https://agent-elements.21st.dev/docs/search-tool Install: `npx shadcn@latest add https://agent-elements.21st.dev/r/search-tool.json` ### Code ```tsx import { SearchTool } from "@/components/agent-elements/tools/search-tool"; const mockResults = { results: [ { source: "google", title: "United UA837 SFO→NRT · $1,105 economy", date: "google.com/flights" }, { source: "expedia", title: "SFO–Tokyo · 14 results from $1,089", date: "expedia.com" }, ], }; const part = { type: "tool-WebSearch", toolCallId: "search-1", state: "output-available", input: { query: "best flights to Tokyo" }, output: mockResults, }; export function Example() { return ; } ``` ### API Reference ```ts type SearchToolProps = { part: any; results?: SearchResult[]; defaultOpen?: boolean; }; ``` ### Usage Render grouped search results. Provide input.query/pattern and output.results (or pass results directly with the results prop). Use defaultOpen to keep it expanded. ### Example: Rich results ```tsx ``` ### Example: Pending search ```tsx const pendingPart = { type: "tool-WebSearch", toolCallId: "search-2", state: "input-streaming", input: { query: "redis sliding window rate limiting" }, }; ``` ### Example: Alt source set ```tsx const altResults = { results: [ { source: "arxiv", title: "Quantum error correction below threshold · Acharya 2024", date: "arxiv.org" }, { source: "scholar", title: "Utility of quantum computing · Kim et al · 567 cites", date: "scholar.google.com" }, ], }; const altPart = { type: "tool-WebSearch", toolCallId: "search-3", state: "output-available", input: { query: "quantum error correction" }, output: altResults, }; ``` --- ## TodoTool URL: https://agent-elements.21st.dev/docs/todo-tool Install: `npx shadcn@latest add https://agent-elements.21st.dev/r/todo-tool.json` ### Code ```tsx import { TodoTool } from "@/components/agent-elements/tools/todo-tool"; const part = { type: "tool-TodoWrite", toolCallId: "todo-1", state: "output-available", input: { todos: [ { content: "Audit components", status: "completed" }, { content: "Tighten spacing", status: "in_progress", activeForm: "Tightening spacing" }, { content: "Ship updates", status: "pending" }, ], }, output: { oldTodos: [] }, }; export function Example() { return ; } ``` ### Usage Render task list changes from input.todos, optionally diffed against output.oldTodos. ### Example: New list ```tsx const newListPart = { type: "tool-TodoWrite", toolCallId: "todo-1", state: "output-available", input: { todos: [ { content: "Audit components", status: "completed" }, { content: "Tighten spacing", status: "in_progress", activeForm: "Tightening spacing" }, { content: "Ship updates", status: "pending" }, ], }, output: { oldTodos: [] }, }; ``` ### Example: Single update ```tsx const singleUpdatePart = { type: "tool-TodoWrite", toolCallId: "todo-2", state: "output-available", input: { todos: [ { content: "Audit components", status: "completed" }, { content: "Tighten spacing", status: "completed" }, { content: "Ship updates", status: "pending" }, ], }, output: { oldTodos: [ { content: "Audit components", status: "completed" }, { content: "Tighten spacing", status: "in_progress" }, { content: "Ship updates", status: "pending" }, ], }, }; ``` ### Example: Multiple updates ```tsx const multipleUpdatePart = { type: "tool-TodoWrite", toolCallId: "todo-3", state: "output-available", input: { todos: [ { content: "Audit components", status: "completed" }, { content: "Tighten spacing", status: "completed" }, { content: "Ship updates", status: "in_progress" }, ], }, output: { oldTodos: [ { content: "Audit components", status: "completed" }, { content: "Tighten spacing", status: "pending" }, { content: "Ship updates", status: "pending" }, ], }, }; ``` ### Example: Pending update ```tsx const pendingPart = { type: "tool-TodoWrite", toolCallId: "todo-4", state: "input-streaming", input: { todos: [{ content: "Ship updates", status: "in_progress" }] }, output: { oldTodos: [{ content: "Ship updates", status: "pending" }] }, }; ``` --- ## PlanTool URL: https://agent-elements.21st.dev/docs/plan-tool Install: `npx shadcn@latest add https://agent-elements.21st.dev/r/plan-tool.json` ### Code ```tsx import { PlanTool } from "@/components/agent-elements/tools/plan-tool"; const part = { type: "tool-PlanWrite", toolCallId: "plan-1", state: "output-available", input: { plan: { id: "plan-1", title: "Refresh UI previews", summary: "Unify tool card spacing and interaction patterns so docs previews feel cohesive across all tool components.\n\n1. Standardize card chrome (header height, borders, radius, and muted labels) for Plan, Approval, Edit, Search, and Todo previews.\n2. Align content density and typography so title, metadata, and body text read consistently at a glance.\n3. Normalize interaction states: loading shimmer, pending indicators, hover affordances, and disabled action buttons.\n4. Validate responsive behavior on narrow widths, including truncation rules and action-row wrapping.\n5. Run a visual QA pass in both light and dark themes and tighten spacing where cards feel too loose or cramped.\n\nOutcome: preview gallery feels intentionally designed, easier to scan, and stable across viewport sizes.", }, }, }; export function Example() { return ; } ``` ### Usage Display a plan title and summary with expand/collapse. Set input.approved to hide approval controls. ### Example: In progress ```tsx ``` ### Example: Approved ```tsx const approvedPart = { type: "tool-PlanWrite", toolCallId: "plan-2", state: "output-available", input: { approved: true, plan: { id: "plan-2", title: "Gateway rollout", summary: "Plan approved and ready to execute." }, }, }; ``` ### Example: Pending update ```tsx const pendingPart = { type: "tool-PlanWrite", toolCallId: "plan-4", state: "input-streaming", input: { plan: { id: "plan-4", title: "Expand tool docs", summary: "Drafting an updated plan..." } }, }; ``` --- ## ToolGroup URL: https://agent-elements.21st.dev/docs/tool-group Install: `npx shadcn@latest add https://agent-elements.21st.dev/r/tool-group.json` ### Code ```tsx import { ToolGroup } from "@/components/agent-elements/tools/tool-group"; const part = { type: "tool-Task", toolCallId: "task-1", state: "output-available", input: { description: "Collect previews", subagent_type: "explore" }, output: { totalDurationMs: 6200 }, }; const nestedTools = [ { type: "tool-Bash", state: "output-available", input: { command: "pnpm lint" } }, { type: "tool-Grep", state: "output-available", input: { pattern: "InputBar" } }, { type: "tool-Read", state: "output-available", input: { file_path: "/lib/agent-ui/components/input-bar.tsx" } }, ]; export function Example() { return ( ); } ``` ### API Reference ```ts type ToolGroupProps = { part: any; nestedTools?: any[]; chatStatus?: string; completeLabel: string; shimmerLabel?: string; interruptedLabel: string; maxVisibleTools?: number; defaultOpen?: boolean; showElapsed?: boolean; }; ``` ### Usage Summarize task runs with optional nested tools. Use defaultOpen for initial expand state, maxVisibleTools for streaming height, and showElapsed to hide/show elapsed time. ### Example: Completed with tools ```tsx ``` ### Example: Streaming demo ```tsx ``` ### Example: Interrupted ```tsx ``` --- ## SubagentTool URL: https://agent-elements.21st.dev/docs/subagent-tool Install: `npx shadcn@latest add https://agent-elements.21st.dev/r/subagent-tool.json` ### Code ```tsx import { SubagentTool } from "@/components/agent-elements/tools/subagent-tool"; const part = { type: "tool-Task", toolCallId: "task-1", state: "output-available", input: { description: "Collect previews", subagent_type: "explore" }, output: { totalDurationMs: 6200 }, }; const nestedTools = [ { type: "tool-Bash", state: "output-available", input: { command: "pnpm lint" } }, { type: "tool-Grep", state: "output-available", input: { pattern: "InputBar" } }, { type: "tool-Read", state: "output-available", input: { file_path: "/lib/agent-ui/components/input-bar.tsx" } }, ]; export function Example() { return ; } ``` ### Usage Render a task with nested tool calls. Shows elapsed time and the last nested tool while running. ### Example: Completed ```tsx ``` ### Example: Pending ```tsx ``` ### Example: Interrupted ```tsx ``` --- ## McpTool URL: https://agent-elements.21st.dev/docs/mcp-tool Install: `npx shadcn@latest add https://agent-elements.21st.dev/r/mcp-tool.json` ### Code ```tsx import { McpTool } from "@/components/agent-elements/tools/mcp-tool"; import { parseMcpToolType } from "@/components/agent-elements/tools/tool-registry"; const mcpInfo = parseMcpToolType("tool-ListMcpResources"); const part = { type: "tool-ListMcpResources", toolCallId: "mcp-1", state: "output-available", input: { query: "resources" }, output: [ { type: "text", text: "[{\"id\":\"res_1\",\"name\":\"Billing\"},{\"id\":\"res_2\",\"name\":\"Support\"}]" }, ], }; export function Example() { return ; } ``` ### API Reference ```ts type McpToolProps = { part: any; mcpInfo: McpToolInfo; chatStatus?: string; defaultOpen?: boolean; }; ``` ### Usage Render MCP tool calls with expandable output. Provide part + mcpInfo from parseMcpToolType, use chatStatus to reflect streaming/interrupted state, and defaultOpen to keep output expanded. ### Example: Completed output ```tsx ``` ### Example: Pending ```tsx const pendingPart = { type: "tool-ListMcpResources", toolCallId: "mcp-2", state: "input-streaming", input: { query: "resources" }, }; ``` ### Example: Interrupted ```tsx const interruptedPart = { type: "tool-ListMcpResources", toolCallId: "mcp-3", state: "input-streaming", input: { query: "resources" }, }; ``` --- ## ThinkingTool URL: https://agent-elements.21st.dev/docs/thinking-tool Install: `npx shadcn@latest add https://agent-elements.21st.dev/r/thinking-tool.json` ### Code ```tsx import { ThinkingTool } from "@/components/agent-elements/tools/thinking-tool"; const part = { type: "tool-Thinking", toolCallId: "think-1", state: "output-available", input: { thought: "Reviewing component coverage and preview density." }, }; export function Example() { return ; } ``` ### API Reference ```ts type ThinkingToolProps = { part?: any; step?: Extract; state?: StepState; onComplete?: () => void; defaultOpen?: boolean; expanded?: boolean; onToggleExpand?: () => void; }; ``` ### Usage Render assistant reasoning in a collapsible row. Use defaultOpen for uncontrolled expand, or expanded + onToggleExpand for controlled state. You can also render from mapped step/state/onComplete instead of part. ### Example: Streaming text ```tsx const streamingPart = { type: "tool-Thinking", toolCallId: "think-2", state: "input-streaming", input: { thought: "Drafting a response with tool coverage and previews.\n" + "First outline the sections, then refine the examples and polish copy.\n" + "Keep the final response concise and actionable.", }, }; ``` ### Example: Collapsed ```tsx ``` --- ## GenericTool URL: https://agent-elements.21st.dev/docs/generic-tool Install: `npx shadcn@latest add https://agent-elements.21st.dev/r/generic-tool.json` ### Code ```tsx import { GenericTool } from "@/components/agent-elements/tools/generic-tool"; export function Example() { return ( ); } ``` ### API Reference ```ts type GenericToolProps = { icon?: React.ComponentType<{ className?: string }>; title: string; subtitle?: string; isPending: boolean; isError?: boolean; // reserved for compatibility }; ``` ### Usage Render a simple tool row for custom tools. Provide title/subtitle and control loading with isPending. icon lets you pass a custom icon component. ### Example: Completed ```tsx ``` ### Example: Pending ```tsx ``` ### Example: Compatibility flag ```tsx ``` --- ## QuestionTool URL: https://agent-elements.21st.dev/docs/question-tool Install: `npx shadcn@latest add https://agent-elements.21st.dev/r/question-tool.json` ### Code ```tsx import { useState } from "react"; import { QuestionTool } from "@/components/agent-elements/question/question-tool"; const questions = [ { kind: "single", title: "Which direction should I take?", options: [ { id: "small", label: "Small patch" }, { id: "full", label: "Full refactor" }, ], allowCustom: true, }, { kind: "single", title: "How cautious should the rollout be?", options: [ { id: "safe", label: "Safe and incremental" }, { id: "fast", label: "Fast rollout" }, ], allowCustom: true, }, ]; export function Example() { const [questionIndex, setQuestionIndex] = useState(1); const totalQuestions = questions.length; const part = { type: "tool-Question", toolCallId: "question-1", state: "input-available", input: { questions, questionIndex, totalQuestions, onPreviousQuestion: () => setQuestionIndex((prev) => Math.max(1, prev - 1)), onNextQuestion: () => setQuestionIndex((prev) => Math.min(totalQuestions, prev + 1)), submitLabel: "Submit", skipLabel: "Skip", onSubmitAnswer: (answer) => console.log(answer), }, }; return ; } ``` ### Usage Support single, multi, and free-text questions. It auto-advances and summarizes by default; wire questionIndex + totalQuestions for controlled navigation. ### Example: Single choice ```tsx ``` ### Example: Multiple choice ```tsx ``` ### Example: Text answer ```tsx ``` ---