# Bag Review

> A shopping bag review page with quantity steppers, removable line items, and a totals summary.

A shadcn/ui block from the [@esuiss](https://suiss.app/ui?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/esuiss/bag-review).

- Page, with a live preview: https://shadcn.exchange/r/esuiss/bag-review
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/esuiss/bag-review-light-31a926a628ab6db0.png
- Registry item: https://suiss.app/ui/r/new-york-v4/bag-review.json
- Categories: blocks, bag
- Listed: 2026-10-20, source last changed 2026-10-20

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/esuiss/bag-review.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `@esuiss/button`, `@esuiss/stepper`

## Files

### `components/blocks/bag-review.tsx`

```tsx
"use client"

import * as React from "react"

import {
  Stepper,
  StepperDecrement,
  StepperGroup,
  StepperIncrement,
  StepperInput,
  StepperSeparator,
} from "@/components/interactions/stepper"
import { Button } from "@/components/ui/button"

const photo = (id: string) =>
  `https://images.unsplash.com/photo-${id}?w=400&q=80&auto=format&fit=crop`

const initialItems = [
  {
    name: "Phone Pro",
    variant: "256 GB · Cosmic Orange",
    price: 1099,
    image: photo("1714972384975-fbd4f19b1bb3"),
    quantity: 1,
  },
  {
    name: "Pods Studio",
    variant: "Midnight",
    price: 549,
    image: photo("1599669454699-248893623440"),
    quantity: 1,
  },
  {
    name: "Phone Pro Clear Case",
    variant: "With MagCharge",
    price: 49,
    image: photo("1703676311066-34caf2cbee33"),
    quantity: 2,
  },
]

const money = (value: number) =>
  value.toLocaleString("en-US", { style: "currency", currency: "USD" })

export function BagReview() {
  const [items, setItems] = React.useState(initialItems)
  const bag = items.filter((item) => item.quantity > 0)
  const subtotal = bag.reduce(
    (sum, item) => sum + item.price * item.quantity,
    0
  )

  const setQuantity = (name: string, quantity: number) =>
    setItems((current) =>
      current.map((item) => (item.name === name ? { ...item, quantity } : item))
    )

  return (
    <section className="px-5 py-8 md:px-12">
      <div className="mx-auto flex max-w-205 flex-col">
        <h1
          aria-live="polite"
          className="text-center text-3xl font-semibold tracking-tight text-balance md:text-4xl"
        >
          {bag.length
            ? `Your bag total is ${money(subtotal)}.`
            : "Your bag is empty."}
        </h1>
        <p className="mt-2 mb-5 text-center text-base text-muted-foreground">
          Free delivery and free returns on every order.
        </p>
        {bag.length > 0 ? (
          <Button size="lg" className="mb-5 self-center">
            Check Out
          </Button>
        ) : (
          <Button
            size="lg"
            className="self-center"
            onClick={() => setItems(initialItems)}
          >
            Continue Shopping
          </Button>
        )}
        <ul>
          {bag.map((item) => (
            <li
              key={item.name}
              className="grid grid-cols-[5rem_minmax(0,1fr)] gap-x-4 gap-y-3 border-t border-border py-5.5 transition-[opacity,translate] duration-700 starting:translate-y-1.5 starting:opacity-0 motion-reduce:transition-none md:grid-cols-[7.5rem_minmax(0,1fr)_auto_7.5rem] md:gap-5"
            >
              <img
                src={item.image}
                alt=""
                className="size-20 rounded-xl bg-secondary object-cover md:row-span-2 md:size-30"
              />
              <div className="flex flex-col gap-1">
                <h2 className="text-xl font-semibold">{item.name}</h2>
                <p className="text-sm text-muted-foreground">{item.variant}</p>
                <a
                  href="#details"
                  className="mt-1.5 text-sm text-primary hover:underline"
                >
                  Show product details ›
                </a>
              </div>
              <Stepper
                value={item.quantity}
                onValueChange={(value) => setQuantity(item.name, value)}
                min={1}
                max={10}
                className="col-start-2 flex items-center gap-2 md:col-start-auto md:items-start"
              >
                <StepperInput
                  aria-label={`${item.name} quantity`}
                  className="w-8 text-center"
                />
                <StepperGroup>
                  <StepperDecrement />
                  <StepperSeparator />
                  <StepperIncrement />
                </StepperGroup>
              </Stepper>
              <div className="col-start-2 flex items-center justify-between gap-1.5 md:col-start-auto md:flex-col md:items-end md:justify-start">
                <span className="text-xl font-semibold tabular-nums">
                  {money(item.price * item.quantity)}
                </span>
                <Button
                  variant="link"
                  size="sm"
                  onClick={() => setQuantity(item.name, 0)}
                  aria-label={`Remove ${item.name}`}
                  className="h-auto px-0 text-sm text-primary"
                >
                  Remove
                </Button>
              </div>
            </li>
          ))}
        </ul>
        {bag.length > 0 && (
          <div className="flex flex-col gap-2.5 border-t border-border pt-5 md:ps-90">
            <div className="flex justify-between text-base">
              <span>Subtotal</span>
              <span className="tabular-nums">{money(subtotal)}</span>
            </div>
            <div className="flex justify-between text-base">
              <span>Shipping</span>
              <span>FREE</span>
            </div>
            <div className="flex justify-between border-t border-border pt-3 text-2xl font-semibold">
              <span>Total</span>
              <span className="tabular-nums">{money(subtotal)}</span>
            </div>
            <div className="mt-1 flex flex-wrap justify-end gap-2.5">
              <Button
                size="lg"
                className="bg-foreground text-background hover:bg-foreground/90"
              >
                Check Out with <span className="font-bold">suiss</span> Pay
              </Button>
              <Button size="lg">Check Out</Button>
            </div>
          </div>
        )}
      </div>
    </section>
  )
}
```
