Cart

Review the cart before checkout, then hand off to the host's own checkout.

Experience · Shares patterns with Change Preview, Result Receipt

≈ 1,335 tokens per use (tool 835 · guidance 313 · typical payload 187)How token costs are estimated

Example: Cart

OK, I'm ready to check out.

Your cart

  • Black and white running shoe on a beige surface
    Stride Lite 2Size 42 · Black
    1
    Line total $129.00
  • Three rolled grey-green wool trail socks
    Trail Crew Socks, 3-packSize M · $18.00 each
    2
    Line total $36.00
Shipping
ShippingStandard · Wed 14 Oct
Free
Subtotal
$165.00
Total
Taxes calculated at checkout$165.00

When to use

  • The user is ready to check out, or asks to see or change their cart.
  • They should confirm quantities and pick shipping before the store's checkout.

When not to use

  • The user is still choosing a product or its options. Use Product Options or Entity Cards.
  • The user asks about one item's price or whether it is a good deal. Use Price Details.
  • A sentence answers it ("how much is my cart?") and nothing needs changing.
  • You don't have the cart from the store. Never invent items, prices, shipping or discounts.
  • The order is already placed. Show a Result Receipt instead.

How the agent should use it

  • Pass items, prices, shipping and promo exactly as the store's cart returned them, in one currency.
  • Only set promo for a discount the store has already applied.
  • On checkout, update the store's cart to the returned items and shipping, then open the host's checkout. The host recomputes the total.
  • Never collect payment, card or bank details in chat. Payment happens only in the host's checkout.
  • On keep_shopping, leave the cart as it was and help the user find more.

Checkout is a handoff

The Cart never takes payment and has no payment fields. Pressing checkout sends the final lines and shipping choice back, and your app opens its own checkout (a hosted payment page, your checkout route, a native sheet) where the real amount is calculated and charged. total in the result is the figure the user saw, for the agent's reply and your logs; never charge it.

Editing the cart

  • Quantities go from 1 to maxQuantity (default 10, or the current quantity if higher). Remove a line with its remove button; "Undo" brings it back.
  • Shipping shows as one row with the chosen method, its arrival and its price. Change expands the other methods; picking one collapses the list again. With a single method there is nothing to change.
  • Every line is always visible: the item list never scrolls inside the card.
  • Removing the last line shows "Cart is empty" and disables checkout.
  • Totals update as the user edits: subtotal, shipping, the discount (never more than the subtotal) and the total, all in integer minor units so 3 × 2.49 is exactly 7.47. Each change is announced to screen readers with the new total.

The math lives in cart-totals.ts as a pure computeTotals(items, shippingPrice, promoAmount) you can reuse on the server.

Installation

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

Usage

app/api/chat/route.ts
import { tool } from "ai"
import { cartTool } from "@/components/genui/cart.schema"

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

Render the tool call, return the user's decision as the tool result, and open your own checkout:

components/message.tsx
import { Cart } from "@/components/genui/cart"

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

  return (
    <Cart
      {...part.input}
      status={part.state === "input-streaming" ? "streaming" : undefined}
      result={part.state === "output-available" ? part.output : undefined}
      onResult={async (result) => {
        addToolResult({
          tool: "review_cart",
          toolCallId: part.toolCallId,
          output: result,
        })
        if (result.action === "checkout") {
          // Your store's cart and checkout recompute prices and take payment.
          await updateCart(result.items, result.shippingId)
          router.push("/checkout")
        }
      }}
    />
  )
}

The agent receives one of:

{ action: "checkout", items: [{ id: "sku_4471-black-42", quantity: 1 }, { id: "sku_8810-m", quantity: 2 }], shippingId: "standard", total: 165 }
{ action: "keep_shopping" }

Lines the user removed are left out of items.

With LangGraph

Push it from a graph node with ui.push({ name: "review_cart", 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. After a checkout it shows a compact read-only summary and "Sent to checkout"; after keep shopping it shows the cart locked. It never calls onResult again, so the cart can't be sent twice. With the AI SDK, that's the tool call's output:

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

Schema

PropTypeNotes
titlestring?Default "Your cart"
currencystringISO 4217 code for every amount
itemsCartItem[]1–20. Data-bound
shipping{ options: ShippingOption[], selected? }?1–4 options; selected defaults to the first. Data-bound
promo{ code, amount }?A discount already applied. Data-bound
taxNotestring?e.g. "Taxes calculated at checkout"
checkoutLabelstring?Default "Continue to checkout"

CartItem

FieldTypeNotes
idstringReturned in the result
namestring
variantstring?e.g. "Size 42 · Black". Shown on one quiet line with the unit price (unit price only when quantity is above 1)
image{ url, alt }?http(s) only. The row is text-only when missing or broken
unitPricenumber
quantitynumberWhole number, at least 1
maxQuantitynumber?Caps the stepper. Default 10

ShippingOption

FieldTypeNotes
idstringReturned as shippingId
labelstringe.g. "Express"
pricenumber0 shows as Free
etastring?e.g. "Arrives Tue 12 Nov"

Host props: status (streaming, ready, checked_out, kept_shopping), onResult, result and className.

Source

On this page