Inspiration Cards
Visual ideas as image cards with a title and one line, with an optional pick.
≈ 1,150 tokens per use (tool 577 · guidance 293 · typical payload 280)How token costs are estimated
Example: Inspiration Cards
Outfit ideas for a wedding trip to Italy
Your Italy outfit inspiration
Sightseeing chicLinen trousers, a simple top, comfortable sandals.
Wedding guestA terracotta midi dress, block heels, a small clutch.
Evening aperitivoA champagne slip dress with strappy sandals.
Day on the coastA striped sundress, a straw hat, espadrilles.
When to use
- The user asks for visual ideas: outfits, interior styles, recipes, gift ideas, hairstyles, party themes.
- Each idea has an image, a short name and at most one line of detail.
- The user wants to pick the ideas they like before the agent goes further.
When not to use
- The agent doesn't have real image URLs. It must never invent or guess them.
- The items are records to compare by price, rating or specs. Use a Comparison Matrix or an Entity Card.
- One scene-setting collage is enough. Use a Photo Mosaic.
- The topic is fashion, with four or more looks the user may want to browse and save. Use a Style Board.
How the agent should use it
- Take every image
srcandurlfrom a tool result or the host, never from memory. Only http(s) URLs anddata:image/(png|jpeg|webp|svg+xml)images load; a link that isn't http(s) is dropped. - Keep titles to a few words and the line under 90 characters. No marketing copy.
- Use 2–8 cards. Set
columns: 3for six or more compact ideas; otherwise keep the default 2. Phones always show 2. - Write real alt text for each image. On cards that are buttons or links, the title names the card and the alt text describes it.
- Set
selectableonly when the user is choosing, with a specificsubmitLabelandmaxwhen it matters. In selectable mode,urlis ignored. - Lifecycle: ideas are a snapshot, so it usually needs none. When they come from a live catalog with prices, set
fetchedAt(andstaleAfterMs, e.g. a day). When the agent sends a new set, mark the old call superseded (supersededBy).
Interaction
- Display-only (the default): a card with a safe
urlis a link that opens in a new tab and sends{ action: "open", id }for analytics. Cards without one are static. - Selectable: each card is a toggle (
aria-pressed) with a check badge on the image corner and a ring when picked.maxis enforced: once it's reached, the other cards are disabled until one is removed. The footer shows the count ("2 of 3 selected"; with nothing picked, the instruction is read to screen readers only) and the submit button, disabled until something is picked. After submitting, the picked cards stay forward, the others dim, and the footer reads "Chose Soft arches and Low and grounded". - Broken images: an image that fails to load, or whose
srcisn't allowed, becomes a neutral tile showing its alt text. - Streaming: cards append as they arrive, with a placeholder card for the next one; actions are disabled until the call completes. Images load lazily.
Installation
npx shadcn@latest add https://your-site.com/r/inspiration-cards.json
Usage
import { tool } from "ai"
import { inspirationCardsTool } from "@/components/genui/inspiration-cards.schema"
const tools = {
[inspirationCardsTool.name]: tool({
description: inspirationCardsTool.description,
inputSchema: inspirationCardsTool.inputSchema,
}),
}With selectable: "none" the cards are display-only, so give the tool an execute that returns immediately. With "single" or "multiple", the agent receives:
{ action: "select", ids: ["arches", "low"] } // in card orderLink opens send { action: "open", id: "cap" }. They are analytics only and never lock the cards; the agent doesn't need to answer them.
With LangGraph
Push it from a graph node with ui.push({ name: "show_inspiration", props: { toolCallId, ...args } }, { message }), and render the thread's UI messages with the Renderer.
Restoring from history
When a conversation is reloaded, pass the stored result back as result. A select result restores the pick (check badges, rings and the footer) read-only and never calls onResult again, so a choice can't be sent twice. With the AI SDK, that's the tool call's output:
<InspirationCards
{...part.input}
result={part.state === "output-available" ? part.output : undefined}
/>Schema
| Prop | Type | Notes |
|---|---|---|
title | string? | e.g. "Your Italy outfit inspiration" |
columns | 2 | 3 | Default 2. Phones always show 2 |
selectable | "none" | "single" | "multiple" | Default "none" (display-only) |
max | number? | Most picks with "multiple" |
submitLabel | string? | Submit button text. Default "Use these" ("Use this" for single) |
items | Item[] | 2–8 cards, below |
An item is { id, image: { src, alt }, title, line?, url? }. image.src and url are data-bound; line is up to 90 characters.
Host props: status (streaming, ready, selected), onResult, result and className.