Entity Card

Products, places or people the agent found, as cards the user can compare and act on.

Pattern

≈ 1,513 tokens per use (tool 867 · guidance 235 · typical payload 411)How token costs are estimated

Example: Entity Card

Find me running shoes under $150 for road running.

Running shoes under $150

When to use

  • The agent found a few products, places or people, and the user will compare them or pick one.
  • The items have a picture, price, rating or a few short facts worth scanning side by side.

When not to use

  • There's a single result. Describe it in text, or use a Summary Card.
  • The user needs to compare many fields across many rows. Use a Data Table.
  • The items didn't come from a tool. Never invent products, prices or ratings.

How the agent should use it

  • Show 1–8 items, best match first, exactly as the tool returned them.
  • Pick the layout for the content: carousel for visual items the user browses (products), list for items compared by text (places, people).
  • Only set price, rating and badge from the source data. A badge is a real state that changes the choice, like "2 left", never marketing copy or a state every item shares.
  • Keep attributes to 3 short facts the user compares, like "8 yrs", "Go", "Remote".
  • Only add actions the host supports. actions is a closed set: add_to_cart, save, select, book, contact.

Actions

Each button press calls onResult once with { action, entityId }. That button switches to its done state ("Added", "Saved", …) and every other button stays usable, so the user can add two pairs of shoes from the same card set.

Because one card set can produce several actions, handle them in the host rather than as the tool call's single output: give the tool an execute that returns immediately, and act on each press (call the cart API, then tell the agent in a message if it needs to know).

Each item shows its name, one quiet line (subtitle, rating and attributes) and, when set, the price as the largest text. The review count is read to screen readers with the rating ("Rated 4.6 out of 5 from 1,240 reviews") rather than shown.

Images must be http(s); anything else, or an image that fails to load, shows a neutral placeholder with the item's first letter. In the carousel, cards snap as the user scrolls by touch or trackpad, and previous/next buttons appear on larger screens when there's more to see.

Installation

npx shadcn@latest add https://your-site.com/r/entity-card.json

Usage

app/api/chat/route.ts
import { tool } from "ai"
import { entityCardTool } from "@/components/genui/entity-card.schema"

const tools = {
  [entityCardTool.name]: tool({
    description: entityCardTool.description,
    inputSchema: entityCardTool.inputSchema,
    execute: async () => ({ shown: true }),
  }),
}
components/message.tsx
<EntityCard
  {...part.input}
  status={part.state === "input-streaming" ? "streaming" : "ready"}
  result={savedActions[part.toolCallId]}
  onResult={async (result) => {
    saveAction(part.toolCallId, result)
    if (result.action === "add_to_cart") await addToCart(result.entityId)
  }}
/>

Each press sends:

{ action: "add_to_cart", entityId: "sku_4471" }

With LangGraph

Push it from a graph node with ui.push({ name: "show_entities", props: { toolCallId, ...args } }, { message }), and render the thread's UI messages with the Renderer.

Restoring from history

Store each action the user takes, and pass them back as result (one result or an array). Those buttons render in their done state and are never sent again; the others stay usable.

<EntityCard {...part.input} result={savedActions[part.toolCallId]} />

Schema

PropTypeNotes
titlestring?e.g. "Running shoes under $150"
layout"carousel" | "list"Default carousel
entitiesEntity[]1–8. Data-bound
actions{ id, label }[]Up to 2. id is one of add_to_cart, save, select, book, contact

Entity

FieldTypeNotes
idstringReturned in results
namestring
subtitlestring?Brand, cuisine or current role
image{ url, alt }?http(s) only. Placeholder when missing or broken
price{ amount, currency }?ISO 4217 currency, formatted for the user's locale
rating{ value, count? }?value from 0 to 5. Shown as "4.6 ★ (1,240)"
attributesstring[]?Up to 3 short facts
badgestring?A real state, up to 20 characters
urlstring?http(s) only. Opens in a new tab

Host props: status (streaming or ready), onResult, result (one or an array) and className.

Source

On this page