shadcn.io is an independent project built on shadcn/ui
One card says what an agent is: its name and model, the instructions it runs on, the tools it can call and what it returns.
Use it where people build, review or pick agents: a settings page, a team of agents side by side, the docs for an agent you ship. Each tool is a row that opens into its input schema, and the schemas show in a Code Block. Agent shows the setup before anything runs; Subagent shows a run that already happened, its tool calls and what it found. In React, Radix UI runs the tool list, so its rows open from the keyboard.
Installation
The install adds Accordion for the tools, Badge for the model, and Code Block with its highlighter for the schemas.
Anatomy

Agent
โโโ AgentHeader (its name and model)
โโโ AgentContent
โโโ AgentInstructions
โโโ AgentTools
โ โโโ AgentTool (one per tool, opens into its schema)
โโโ AgentOutputUsage
import {
Agent, AgentContent, AgentHeader, AgentInstructions, AgentOutput,
AgentTool, AgentTools,
} from "@/components/ui/agent";<Agent>
<AgentHeader model="gpt-4o" name="Research Assistant" />
<AgentContent>
<AgentInstructions>Search the web and cite every source.</AgentInstructions>
<AgentTools>
<AgentTool
tool={{
description: "Search the web",
jsonSchema: { properties: { query: { type: "string" } }, type: "object" },
}}
value="search"
/>
</AgentTools>
<AgentOutput schema="interface Result { answer: string }" />
</AgentContent>
</Agent>Accessibility
- Tab
- Moves focus to the next tool's row, then into an open schema and its copy button.
- Enter
- Opens or closes the focused tool.
- Space
- Opens or closes the focused tool.
- Each tool's row is a button with
aria-expanded, and its description is the button's name. - A schema scrolls inside its block, and the block can take focus, so a long schema scrolls from the keyboard. Its copy button shows on focus as well as on hover.
- Schemas always read left to right. The card itself follows the page direction.
- The bot icon is hidden from assistive tech: the name says which agent it is.
Demos
Controlled
Three tools of a support agent, one open, with an Expand all button in the header. AgentTools takes the props of Radix UI's accordion, so the demo keeps which tools are open and sets them all at once. Whatever you pass as children to AgentHeader goes after the name, here the button. Open a tool from code when the reader came to see that one, from a link or a failed call.
Team
Three agents in a column, each with its model and the number of tools it has. A card without AgentTools or AgentOutput shows only what it has, here the instructions. Use it to compare agents that share a task, such as a researcher, a writer and a reviewer. Keep each one's instructions short enough to read in the card.
AI SDK Tools
Two tools written the way the AI SDK writes them, description and an inputSchema wrapped by jsonSchema(), and an output schema in JSON. AgentTool reads jsonSchema, then inputSchema, and unwraps the helper's wrapper, so a tools record renders as it is. language on AgentOutput sets its highlighting: TypeScript by default, json here.
AI SDK
A tool from the AI SDK's tool() is a description and an input schema, which is what AgentTool takes. Map the record you hand the model, keyed by name:
import { jsonSchema, tool } from "ai";
const tools = {
getWeather: tool({
description: "Get the current weather for a city",
inputSchema: jsonSchema<{ city: string }>({
properties: { city: { type: "string" } },
required: ["city"],
type: "object",
}),
}),
};
export function WeatherAgentTools() {
return (
<AgentTools>
{Object.entries(tools).map(([name, definition]) => (
<AgentTool key={name} tool={definition} value={name} />
))}
</AgentTools>
);
}A Zod schema isn't JSON Schema yet: convert it first, with z.toJSONSchema(schema) in Zod 4, and pass the result as jsonSchema. Show the model id you pass to the provider in AgentHeader's model, and the system prompt in AgentInstructions.
Migrating from AI SDK Elements
If you added Agent from AI SDK Elementscomponents/ai-elements/agent.tsx. Install Agent from this page, change the imports from @/components/ai-elements/agent to @/components/ui/agent, and delete the old file. The parts keep their names and props. What changes:
tooltakes any object with adescriptionand ajsonSchemaorinputSchema, so the AI SDK'sToolstill fits, and the file no longer importsai.- Schemas show in Code Block, highlighted, with a copy button.
AgentOutputtakeslanguage, TypeScript by default. AgentInstructions,AgentToolsandAgentOutputtakelabelfor their headings, andAgentHeaderrenders its children after the model.
AgentTools is Radix's Accordion: type is "multiple" unless you pass "single", so type="multiple" can stay or go.
FAQ
How do I list an agent's tools in React?
Put one AgentTool per tool inside AgentTools, each with a unique value and a tool object. The row shows the tool's description, or No description, and opens into its input schema as JSON in a Code Block. Several tools can be open at once. AgentTool reads jsonSchema first, then inputSchema.
Can I pass AI SDK tools to Agent?
Yes. A tool made with the AI SDK's tool() has a description and an inputSchema, which is what AgentTool reads, and the wrapper that jsonSchema() adds is unwrapped for you. A Zod schema has to become JSON Schema first, with z.toJSONSchema in Zod 4, or the block shows Zod's internals instead of the fields.
What is the difference between Agent and Subagent?
Agent is the setup: the model, the instructions, the tools it may call and what it returns, before it runs. Subagent is a run: one line in a reply that opens into the calls a subagent made and what it found. Show Agent in settings and docs, and Subagent in the conversation.
How do I show what an agent returns?
Pass the schema as text to AgentOutput's schema prop: a TypeScript interface, a JSON Schema, or the source of a Zod schema. It's highlighted as TypeScript unless you set language, such as json. A long schema scrolls inside its block, and the copy button copies all of it.
How do I open a tool from code?
Control AgentTools as an accordion: pass value, the open tools' values, and update it from onValueChange, as the Controlled demo does.
Open the tool a link points at, or the one whose call failed, rather than all of them.
How do I style an Agent with Radix UI?
Edit the agent file under components/ui. Every part has its own data-slot, from agent and agent-header to agent-tool and agent-output, and the model badge is agent-model. The tool rows are your accordion, and the schemas are Code Blocks, so their styles come from those files. Pass className to any part for a one-off change.
API Reference
Agent
<div> data-slot="agent"
className?string
Merged with the part's classes.
AgentHeader
<div> data-slot="agent-header"
namestring
The agent's name.
model?string
The model it runs on, shown as a badge after its name.
className?string
Merged with the part's classes.
AgentContent
<div> data-slot="agent-content"
className?string
Merged with the part's classes.
AgentInstructions
<div> data-slot="agent-instructions"
childrenstring
The system prompt.
label?string
The heading above it.
- Default
"Instructions"
className?string
Merged with the part's classes.
AgentTools
<div> data-slot="agent-tools"
label?string
The heading above the list.
- Default
"Tools"
type?"multiple"
"multiple" by default: any number of tools open at once.
className?string
Merged with the part's classes.
AgentTool
data-slot="agent-tool"
toolobject
The tool: its description is the row, its input schema the panel.
- Type
{ description?: string; inputSchema?: unknown; jsonSchema?: unknown }
valuestring
A string value for the accordion item. All items within an accordion should use a unique value.
disabled?boolean
Whether or not an accordion item is disabled from user interaction.
asChild?boolean
Merge props onto the child element instead of rendering a host.
className?string
Merged with the part's classes.
AgentOutput
<div> data-slot="agent-output"
schemastring
What the agent returns: a type or a JSON Schema, as text.
label?string
The heading above it.
- Default
"Output Schema"
language?string
The language the schema is highlighted in.
- Default
"typescript"
className?string
Merged with the part's classes.











