Product Card
A product tile for grids and carousels, with pricing, rating, swatches, and stock state built in.

Ceramic Pour-Over Mug
Handcrafted from natural stoneware clay, this minimal pour-over mug features a built-in ceramic dripper for slow, single-cup brewing. Dishwasher safe and made to last.
-30%Linen Table Runner
Woven from 100% European flax linen, this table runner adds effortless texture to any setting. Pre-washed for a soft, lived-in drape. Machine washable and naturally durable.
Out of stockOak Serving Board
A brief description of an oak serving board for an e-commerce store
Sale pricing is derived
There's no onSale flag: passing compareAtPrice is what produces the badge, the struck original, and the discounted price — and the saving is computed from the two numbers, so it can't contradict them.
-27%Ceramic Pour-Over Mug
Colour options

Ceramic Pour-Over Mug
Out of stock
Out of stockCeramic Pour-Over Mug
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| image / imageAlt | string | Product photo and its alt text. | |
| name | string | Product name. | |
| description | string | Short supporting line. | |
| price | number | Raw number, formatted by the component. | |
| compareAtPrice | number | The was-price. Its presence switches the card to the sale treatment. | |
| rating / reviewCount | number | Star rating out of 5 and the number of reviews behind it. | |
| colors | ProductCardColor[] | Swatches: { id?, color, label, available? }. id falls back to label. | |
| onColorSelect | (id: string) => void | Fires with the selected swatch's id. | |
| inStock | boolean | False dims the card and blocks the add action. | |
| wishlisted | boolean | Fills the wishlist heart. |
Why it works this way
Why
- A sale badge and struck-through price appear whenever a compare-at price is given, so the discount is derived from data rather than set by hand.
Don't
- Don't set a compare-at price that is not a real previous price.