Product Options

Choose a product's size, color or other options and a quantity, then add it to the cart.

Experience · Shares patterns with Option Builder, Entity Picker

≈ 2,154 tokens per use (tool 1,184 · guidance 297 · typical payload 673)How token costs are estimated

Example: Product Options

I'll take the Stride Lite 2.

Black and white running shoe on a beige surface

Stride Lite 2

Rated 4.6 out of 5 from 1,240 reviews
Size
Color
Quantity
1
Total
$129.00

Choose size and color

When to use

  • The user wants a specific product that comes in options (size, color, grind, bag size) and is ready to add it to the cart.
  • The catalog gave you the options, prices and stock for that product.

When not to use

  • The user is still comparing several products. Show them as Entity Cards first.
  • The product has no options. Add it directly, or use an Entity Card's add_to_cart action.
  • You don't have the options or stock from the catalog. Never invent sizes, prices or availability.
  • The user wants to pay. This component only adds to the cart; payment happens in the host's own checkout.

How the agent should use it

  • Pass options, prices, priceDelta and variants exactly as the catalog returned them.
  • Give variants with stock whenever the catalog has them, so out-of-stock combinations are disabled.
  • Set compareAt only when the catalog reports an original price.
  • On add_to_cart, add exactly the returned variantId (or options) and quantity through the cart API, then confirm in one line.
  • On cancel, add nothing.
  • Never ask for card or payment details in chat.

How availability works

  • A value marked available: false is out of stock in every combination: it's greyed out and can't be chosen. A color value can carry a swatch hex, shown as a dot.
  • With variants, a value is only available when an in-stock variant matches it and the user's other choices. Choosing "Ember" crosses out the sizes that aren't in stock in Ember, so the user can never reach a sold-out combination. Combinations missing from variants count as out of stock.
  • A variant with stock of 5 or fewer shows "Only N left", and stock caps the quantity.
  • A group with a single in-stock value is chosen automatically.
  • The unit price adds each chosen value's priceDelta; the total multiplies by the quantity. Money is computed in integer minor units, so totals don't drift.
  • The card shows one price: the line total, large, next to the Add to cart button. With a quantity above 1 it notes the unit price ("2 × £18.00"); with compareAt the original line price sits struck through beside it.

The logic lives in product-options-variants.ts as pure functions (isValueAvailable, resolveVariant, priceFor), so you can reuse it on the server.

Installation

npx shadcn@latest add https://your-site.com/r/product-options.json

Usage

app/api/chat/route.ts
import { tool } from "ai"
import { productOptionsTool } from "@/components/genui/product-options.schema"

const tools = {
  // No execute: the user's choice is the tool result.
  [productOptionsTool.name]: tool({
    description: productOptionsTool.description,
    inputSchema: productOptionsTool.inputSchema,
  }),
}

Render the tool call, and return the user's choice as the tool result:

components/message.tsx
import { ProductOptions } from "@/components/genui/product-options"

function ToolPart({ part, addToolResult }) {
  if (part.type !== "tool-choose_product_options") return null

  return (
    <ProductOptions
      {...part.input}
      status={part.state === "input-streaming" ? "streaming" : undefined}
      result={part.state === "output-available" ? part.output : undefined}
      onResult={(result) =>
        addToolResult({
          tool: "choose_product_options",
          toolCallId: part.toolCallId,
          output: result,
        })
      }
    />
  )
}

The agent receives one of:

{ action: "add_to_cart", productId: "sku_4471", variantId: "sku_4471-black-42", options: { size: "42", color: "black" }, quantity: 1 }
{ action: "cancel" }

Add the item through your cart API with the returned ids. Don't trust prices from the client: the cart and checkout look them up again.

With LangGraph

Push it from a graph node with ui.push({ name: "choose_product_options", 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. The component shows the chosen options and "Added to cart" (or that it was cancelled) and never calls onResult again, so the item can't be added twice. With the AI SDK, that's the tool call's output:

<ProductOptions
  {...part.input}
  result={part.state === "output-available" ? part.output : undefined}
/>

Schema

PropTypeNotes
productProductData-bound
optionsOptionGroup[]1–3 groups. Data-bound
variants{ id, options, available, stock? }[]?Every combination sold, e.g. options: { size: "42", color: "black" }. Data-bound
quantity{ min?, max?, default? }?Default 1–10, starting at min
submitLabelstring?Default "Add to cart"

Product

FieldTypeNotes
idstringReturned as productId
namestring
image{ url, alt }?http(s) only. Placeholder when missing or broken
price{ amount, currency }Base unit price. ISO 4217 currency
compareAt{ amount }?Original price, shown struck through
rating{ value, count? }?value from 0 to 5

OptionGroup

FieldTypeNotes
idstringe.g. size. Keys the result's options
labelstringe.g. "Size"
values{ value, label, available?, priceDelta? }[]1–16. priceDelta is added to the unit price

Host props: status (streaming, ready, added, cancelled), onResult, result and className.

Source

On this page