Cart Summary
NewA checkout card with quantity steppers, removable lines, a promo field that shakes on a wrong code, rolling totals and a free shipping bar.
motionlucide-reactutilsbuttonbutton-groupcardemptyfieldinput-groupitemprogressseparatorspinner@soultel/rolling-number@soultel/use-reduced-motion-safe
Try the code SAVE10
Installation
$ pnpm dlx shadcn@latest add @soultel/cart-summaryNo namespace set up? npx shadcn@latest add https://asafefe.dev/r/cart-summary.json works too.
Manual installation
- 1. Install the dependencies.
terminal1 lines npm install motion lucide-react - 2. Copy the source from the Code tab into components/cart-summary.tsx.
Usage
import { CartSummary } from "@/components/cart-summary"
export function Checkout() {
return (
<CartSummary
defaultItems={[
{ id: "tee", name: "Heavyweight Tee", variant: "Oat, M", price: 32, quantity: 2, color: "#c9a77c" },
{ id: "mug", name: "Stoneware Mug", variant: "Ember, 12 oz", price: 18, quantity: 1, color: "#e2412f" },
]}
promoCodes={{ SAVE10: { percent: 10 }, WELCOME5: { amount: 5, label: "$5 off your first order" } }}
onCheckout={(items, totals) => startCheckout(items, totals)}
/>
)
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
| items / defaultItems | CartItem[] | - | Controlled or initial lines: id, name, price, quantity, and optional variant, max, color, image. |
| onItemsChange | (items: CartItem[]) => void | - | Called when a quantity changes or a line is removed. |
| promoCodes | Record<string, PromoCode> | {} | Valid codes, matched without case: { percent } or { amount }, with an optional label. |
| freeShippingThreshold | number | 75 | Subtotal after discount that makes shipping free. 0 hides the bar. |
| shippingCost | number | 6.95 | Flat shipping below the threshold. |
| currency | string | "$" | Symbol shown before prices. |
| onCheckout | (items, totals) => void | Promise<void> | - | Called with the lines and subtotal, discount, shipping, total and promo. A promise shows a pending state. |
