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, Base 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 Base 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 Base UI's Accordion: multiple is on by default, so drop type="multiple". A controlled list takes value and onValueChange, which gets the open values.
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 Base 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"
value?AccordionValue<Value>
The controlled value of the item(s) that should be expanded.
To render an uncontrolled accordion, use the defaultValue prop instead.
- Type
AccordionValue<Value>
defaultValue?AccordionValue<Value>
The uncontrolled value of the item(s) that should be initially expanded.
To render a controlled accordion, use the value prop instead.
- Type
AccordionValue<Value>
onValueChange?(value: AccordionValue<Value>, eventDetails: AccordionRootChangeEventDetails) → void
Event handler called when an accordion item is expanded or collapsed. Provides the new value as an argument.
- Type
(value: AccordionValue<Value>, eventDetails: AccordionRootChangeEventDetails) → void- Parameters
value– AccordionValue<Value>eventDetails– AccordionRootChangeEventDetails- Returns
void
disabled?boolean
Whether the component should ignore user interaction.
- Default
false
hiddenUntilFound?boolean
Allows the browser's built-in page search to find and expand the panel contents.
Overrides the keepMounted prop and uses hidden="until-found"
to hide the element without removing it from the DOM.
- Default
false
keepMounted?boolean
Whether to keep the element in the DOM while the panel is closed.
This prop is ignored when hiddenUntilFound is used.
- Default
false
label?string
The heading above the list.
- Default
"Tools"
loopFocus?boolean
Deprecated following the [APG guidance update](https://github.com/w3c/aria-practices/pull/3434) to remove roving focus. This prop no longer affects keyboard focus behavior. Deprecated.
multiple?boolean
Whether multiple items can be open at the same time. Ark default: false.
- Default
true
orientation?"horizontal" | "vertical"
Deprecated following the [APG guidance update](https://github.com/w3c/aria-practices/pull/3434) to remove roving focus. This prop no longer affects keyboard focus behavior. Deprecated.
- Type
"horizontal" | "vertical"- Default
"vertical"
render?ReactElement | ((props: object, state: object) => ReactElement)
Replace the host element or compose it with another component.
- Type
ReactElement | ((props: object, state: object) => ReactElement)
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 }
disabled?boolean
Whether the component should ignore user interaction.
- Default
false
onOpenChange?(open: boolean, eventDetails: AccordionItem.ChangeEventDetails) → void
Event handler called when the panel is opened or closed.
- Type
(open: boolean, eventDetails: AccordionItem.ChangeEventDetails) → void- Parameters
open– booleaneventDetails– AccordionItem.ChangeEventDetails- Returns
void
value?any
A unique value that identifies this accordion item.
If no value is provided, a unique ID will be generated automatically.
Use when controlling the accordion programmatically, or to set an initial
open state.
``tsx
<Accordion.Root value={[a]}>
<Accordion.Item value="a" /> // initially open
<Accordion.Item value="b" /> // initially closed
</Accordion.Root>
``
render?ReactElement | ((props: object, state: object) => ReactElement)
Replace the host element or compose it with another component.
- Type
ReactElement | ((props: object, state: object) => ReactElement)
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.











