# Product cards

> Five storefront pieces — swatch card, quick-add card, bag row, bundle deal and compact ranked list — with recolouring products, rolling quantities and rows that glide to a new sort.

- Collection: Examples
- Tier: pro
- Docs: https://geturui.com/docs/product-cards
- Install: `npx shadcn@latest add @geturui-pro/product-cards`
- Dependencies: motion, lucide-react

## Variants

### Classic

Product card whose drawn item recolours as you pick a swatch, with a popping wishlist heart and Add to bag that confirms.

Install: `npx shadcn@latest add @geturui-pro/product-cards-classic`

```tsx
import { ClassicProductCard } from "@/components/examples/product-cards/classic";

export function Bottle() {
  return <ClassicProductCard colors={[{ name: "Moss", hex: "#5f7a3a" }, { name: "Clay", hex: "#c4643d" }]} onAddToBag={(c) => add(c.name)} />;
}
```

| Prop | Type | Default | Description |
|---|---|---|---|
| name / description / price | `string` | — | Product content. |
| compareAt / saveLabel | `string` | — | Struck-through price and the money tag. |
| rating / reviews | `number` | — | Average rating and review count. |
| colors / defaultColor | `ProductColor[] / string` | — | { name, hex } swatches and the initial one. |
| defaultWishlisted / onWishlistChange | `boolean / (w: boolean) => void` | — | Wishlist state. |
| onAddToBag | `(color: ProductColor) => void` | — | Called by Add to bag. |
| className | `string` | — | Extra classes on the root element. |

### Quick add

Card whose size picker slides up over the image on hover, focus or tap, adds in one click and confirms in place.

Install: `npx shadcn@latest add @geturui-pro/product-cards-quick-add`

```tsx
import { QuickAddCard } from "@/components/examples/product-cards/quick-add";

export function Sweater() {
  return <QuickAddCard name="Merino crew sweater" price="$118" onAdd={(size) => addToBag("merino-crew", size)} />;
}
```

| Prop | Type | Default | Description |
|---|---|---|---|
| name / detail / price | `string` | — | Product content. |
| tone | `string` | "#8a7f6c" | Hex used to draw the garment. |
| sizes | `SizeOption[]` | — | { label, inStock? } per size; sold-out sizes are disabled. |
| defaultOpen | `boolean` | false | Start with the size panel open. |
| onAdd | `(size: string) => void` | — | Called with the chosen size. |
| className | `string` | — | Extra classes on the root element. |

### Bag row

Bag row with a quantity stepper whose number rolls, a following line total, a low-stock warning and remove with undo.

Install: `npx shadcn@latest add @geturui-pro/product-cards-list-row`

```tsx
import { ProductListRow } from "@/components/examples/product-cards/list-row";

export function BagLine() {
  return <ProductListRow name="Stoneware pour-over set" unitPrice={54} stock={4} onQuantityChange={(q) => update(q)} onRemove={remove} />;
}
```

| Prop | Type | Default | Description |
|---|---|---|---|
| name / variant | `string` | — | Item name and variant line. |
| unitPrice / currency | `number / string` | — | Price per unit and ISO currency. |
| defaultQuantity / stock | `number` | — | Initial quantity and the stepper cap. |
| tone / shape | `string / "vessel" | "box"` | — | Thumbnail colour and drawing. |
| onQuantityChange / onRemove / onUndo | `(q: number) => void / () => void` | — | Row handlers. |
| className | `string` | — | Extra classes on the root element. |

### Bundle deal

Bundle card where toggling items recounts the price and savings under a live countdown with rolling digits.

Install: `npx shadcn@latest add @geturui-pro/product-cards-bundle-deal`

```tsx
import { BundleDealCard } from "@/components/examples/product-cards/bundle-deal";

export function Bundle() {
  return <BundleDealCard endsIn={secondsUntilMidnight} onAdd={(ids, total) => addBundle(ids, total)} />;
}
```

| Prop | Type | Default | Description |
|---|---|---|---|
| title | `string` | "Weekend brew bundle" | Card heading. |
| items | `BundleItem[]` | — | { id, name, price, art } per item. |
| tiers | `Record<number, number>` | { 2: 10, 3: 20 } | Discount percent by number of items picked. |
| endsIn | `number` | — | Seconds until the deal ends. |
| currency | `string` | "USD" | ISO currency for formatting. |
| onAdd | `(ids: string[], total: number) => void` | — | Called by the add button. |
| className | `string` | — | Extra classes on the root element. |

### Compact list

Ranked list of four products whose rows glide to their new order when the sort changes, each with an add toggle.

Install: `npx shadcn@latest add @geturui-pro/product-cards-compact-list`

```tsx
import { CompactProductList } from "@/components/examples/product-cards/compact-list";

export function Trending({ products }) {
  return <CompactProductList products={products} defaultSort="popular" onAdd={(p, added) => toggleBag(p.id, added)} />;
}
```

| Prop | Type | Default | Description |
|---|---|---|---|
| heading | `string` | "Trending this week" | List title. |
| products | `CompactProduct[]` | — | { id, name, maker, price, rating, sold, tone } per row. |
| defaultSort | `"popular" | "price" | "rating"` | "popular" | Initial order. |
| currency | `string` | "USD" | ISO currency for formatting. |
| onAdd | `(product: CompactProduct, added: boolean) => void` | — | Called when a row is added or removed. |
| className | `string` | — | Extra classes on the root element. |

## Accessibility

- Swatches and sorts are labelled radio and pressed-button groups with arrow keys and visible focus.
- Quantity, add and remove actions are real buttons with item names in their labels; stock and savings are stated in words.
- Recolours, rolling numbers and reorders run without spatial motion when reduced motion is on.

## Source

Pro item — send `Authorization: Bearer <registry token>` to include the source.
