Elements

Elements · Agents

Agent card

Who you are about to talk to: its skills, its model, and the endpoint behind it.

Maintainerv1.4.0assistant-ui

Works through the issue queue: reproduces the report, writes the fix, and opens the PR.

triageRead an issue and label it
reproBuild a minimal reproduction
patchOpen a PR with the fix
https://agents.example.com/a2aopus
fig. 01

Installation

npx shadcn@latest add "@assistant-ui/elements-agent-card"
First time? Set up a runtime

Runtime components read their state from an assistant-ui runtime. Add one to an existing project:

npx assistant-ui@latest init

Then wrap your app in a runtime provider:

import { AssistantRuntimeProvider } from "@assistant-ui/react";
import { useChatRuntime, AssistantChatTransport } from "@assistant-ui/ai-sdk";

export default function App() {
  const runtime = useChatRuntime({
    transport: new AssistantChatTransport({ api: "/api/chat" }),
  });

  return (
    <AssistantRuntimeProvider runtime={runtime}>
      {/* your components */}
    </AssistantRuntimeProvider>
  );
}

The installation guide covers new projects, templates, and API routes.

An agent card is the identity a remote agent presents before you talk to it: a name, what it can do, and where it lives. With a runtime the card comes from the A2A agent you connected to; standalone you supply the same fields yourself.

Getting started

@assistant-ui/react-a2a fetches the remote agent's card as part of connecting to it, and exposes it through a read-only hook.

Connect to the agent

app/A2AProvider.tsx
"use client";

import { AssistantRuntimeProvider } from "@assistant-ui/react";
import { useA2ARuntime } from "@assistant-ui/react-a2a";

export function A2AProvider({ children }: { children: React.ReactNode }) {
  const runtime = useA2ARuntime({ baseUrl: "https://weather-agent.example.com" });
  return (
    <AssistantRuntimeProvider runtime={runtime}>
      {children}
    </AssistantRuntimeProvider>
  );
}

Render the discovered card

components/assistant-ui/elements/connected-agent-card.tsx
"use client";

import { useA2AAgentCard } from "@assistant-ui/react-a2a";
import { AgentCard } from "@/components/assistant-ui/elements/agent-card";

export function ConnectedAgentCard() {
  const agentCard = useA2AAgentCard();
  if (!agentCard) return null;

  return (
    <AgentCard
      name={agentCard.name}
      description={agentCard.description}
      provider={agentCard.provider?.organization ?? ""}
      version={agentCard.version}
      endpoint={agentCard.supportedInterfaces[0]?.url ?? ""}
      skills={agentCard.skills.map((skill) => ({
        name: skill.name,
        description: skill.description,
      }))}
      model="gpt-5.6-sol"
      connected
    />
  );
}

useA2AAgentCard returns undefined until the client has fetched the card, so the connect check above stands in for a loading state. The A2A protocol has no model identifier on the card itself and no separate connect step beyond reaching the agent, which is why model is a value your app supplies and connected is simply true once the card exists.

Anatomy

<div data-slot="agent-card">
  <div>
    <span>{/* bot icon avatar */}</span>
    <div>
      <span>{name} <span>v{version}</span></span>
      <span>{provider}</span>
    </div>
  </div>
  <p>{description}</p>
  <div>{/* one row per skill: name pill + description */}</div>
  <div>
    <span>{endpoint}</span>
    <span>{model}</span>
  </div>
  <button>{connected ? "Connected" : "Connect"}</button>
</div>

The name, provider, endpoint, and model text all truncate rather than wrap. The button is the only interactive part: while connected is true it renders disabled with a check icon and the label "Connected"; otherwise it calls onConnect on click. An empty skills array simply renders no rows in that section, with no placeholder text.

Examples

Skills list

Each skill is a name and a one-line description; the name renders in the shared field pill token.

<AgentCard
  {...rest}
  skills={[
    { name: "search_docs", description: "Full-text search over indexed documents" },
    { name: "summarize", description: "Condense a document to key points" },
  ]}
/>

Fetching your own agent registry

Restyle the card

The root uses the shared paper surface; the version badge, endpoint, and model use mono; the connected state and skill names use field. Restyling those tokens in surfaces.tsx restyles every element built on them.

<AgentCard className="max-w-md rounded-3xl" {...rest} />

API reference

Hook

HookReturnsDescription
useA2AAgentCard()A2AAgentCard | undefinedThe card fetched for the connected agent; undefined before it resolves.

Mapping A2AAgentCard onto AgentCard

AgentCard propSource
nameagentCard.name
descriptionagentCard.description
provideragentCard.provider?.organization
versionagentCard.version
endpointagentCard.supportedInterfaces[0]?.url
skillsagentCard.skills.map(s => ({ name: s.name, description: s.description }))
modelNot present on A2AAgentCard; supply your own label.
connectedtrue once agentCard is defined; A2A has no separate connect step.