Confirm an add to cart
Every way into the cart goes through one hook, useAddToCart: the add to cart button,
a reorder, a quote ordered into the cart, a shopping list, and your own forms. So the
confirmation is set once, for the whole site, and every add shows it.
| What you want | Use |
|---|---|
| A sheet with the product, its price and suggestions to go with it | The shipped sheet |
| A toast, a dialog or a mini-cart of your own | Your own confirmation |
| Something else on one page, such as a cross-sell dialog | One page differently |
| Nothing: the cart badge in the header is enough | Nothing to do. CartBadge already announces the new count. |
The shipped sheet
Section titled “The shipped sheet”CartAddedSheet from @evolve-storefront/feature-cart
is the confirmation the package ships. Mount it once, inside your CartProvider:
import { CartAddedSheet } from "@evolve-storefront/feature-cart/cart-added-sheet";import { CartProvider } from "@evolve-storefront/feature-cart/provider";import Link from "next/link";
<CartProvider> {children} <CartAddedSheet renderCartLink={(children) => <Link href="/cart">{children}</Link>} renderLink={(line, children) => <Link href={`/products/${line.productSlug}`}>{children}</Link>} renderCrossSell={(line) => <RelatedProducts slug={line.productSlug} limit={2} />} /></CartProvider>renderCartLink is the only prop it needs. The sheet then does this on every add:
- One product shows its image, name, the quantity added and its price.
- Several lines at once, such as a reorder, show how many lines and items went in and
what they cost, as gross or net prices, following the provider’s
vatDisplay. - Lines the cart refused are listed with the reason: out of stock, below the minimum quantity, no price, or an unknown product.
- Suggestions come from
renderCrossSell, so the product query stays yours. The sheet asks for them only when a consumer adds one product, never for a bulk add or a business buyer.
It does not close on its own. Escape, the backdrop, “Keep shopping” and “Go to cart”
close it, and focus goes back to the button that added. If you build your own add
button, keep it focusable while it adds: use aria-disabled, not disabled, or focus
has nowhere to go back to.
The sheet is a Sheet from @evolve-storefront/ui, so it takes side and the other
sheet props, and follows your brand tokens.
Your own confirmation
Section titled “Your own confirmation”Leave CartAddedSheet out, and pass onLineItemsAdded to the CartProvider instead.
Use one or the other: with both, the shopper gets two confirmations.
-
Mount the toaster once, in your root layout:
import { Toaster } from "@evolve-storefront/ui/toaster";<Toaster position="bottom-right" /> -
Show a toast from the provider:
import { CartProvider } from "@evolve-storefront/feature-cart/provider";import { toast } from "sonner";<CartProvideronLineItemsAdded={({ addedItems, requested, warnings }) => {const name = addedItems[0]?.productName ?? "";if (warnings.length > 0) {toast.warning(`${name} added, but stock limited the quantity`);return;}toast.success(`${requested[0]?.quantity} × ${name} added to your cart`);}}>{children}</CartProvider>
For a dialog or a mini-cart, keep the result in state and open your own Dialog or
Sheet from @evolve-storefront/ui with it. The reference sites do this. They keep the
added lines in a React context of their own, and their added-to-cart dialog opens
whenever it holds any.
The callback receives one object:
| Field | What it holds |
|---|---|
addedItems |
The cart’s lines for the products just added, after the write. A product that was already in the cart gives its line with the new total quantity. |
requested |
What was asked for. Its quantity is the amount just added: use it to say “2 added”. |
cart |
The cart the write returned. |
warnings |
Problems that did not stop the add. OUT_OF_STOCK means the add worked, but stock capped the quantity. |
failed |
The lines the cart refused, each with its reason. They are not in addedItems. |
One page differently
Section titled “One page differently”A page can replace the confirmation for its own adds. Pass onAdded to the button, or to
the hook when you add from your own form:
import { CartAddProductButton } from "@evolve-storefront/feature-cart/cart-add-product-button";
<CartAddProductButton sku={variant.sku} quantity={quantity} onAdded={(result) => openCrossSellDialog(result.addedItems[0])}/>const add = useAddToCart({ onAdded: (result) => openCrossSellDialog(result.addedItems[0]) });onAdded replaces the site’s confirmation, it does not add to it. Adds with an onAdded
open neither the sheet nor your onLineItemsAdded, so a shopper never sees a cross-sell
dialog and a mini-cart at the same time.
Screen readers
Section titled “Screen readers”The hook announces every add to screen readers, whichever confirmation you choose, and
also when you show none. If a page announces the result itself, pass
useAddToCart({ silent: true }) so it is not announced twice.
Next steps
Section titled “Next steps”- Change how the sheet, or any other component, looks: Customize a component.
- Add more than a quantity, such as a color picked in your own form: see “Add to cart
with your own fields” in
node_modules/@evolve-storefront/feature-cart/CUSTOMIZING.md.

