# Product cards

> A product card is a smootify-product that shows one product in a list. Covers what a card needs, what it can show (price ranges, option values, badges), adding to cart from a card, cards in a slider and placeholder cards.

Status: Ready (Smootify 2.0)  
1.x page: https://docs.smootify.io/docs/ecommerce/products/listing  
URL: https://www.smootify.io/docs/build/product-cards  
Updated: 2026-10-10

A product card shows one product in a list: a Collection list, a search & discovery grid, a slider or a list of best sellers. In Smootify every card is the same element as the product page, `smootify-product`, holding a smaller design. This page covers what a card needs, what it can show, and how to add to the cart from it.

> [!NOTE]
> Before you start, your products must be synced to the **Products** collection, with the `cms:Shopify ID` field filled in. See [CMS collections and sync](https://www.smootify.io/docs/start/cms).

## What a card needs

- **A `smootify-product` element with the product’s id.** In a Collection list, add it inside the Collection item and take `data-id` from the **Shopify ID** field. In the lists Smootify fills itself (search & discovery, the search page, best sellers and the other [product lists](https://www.smootify.io/docs/build/product-lists)), you design one card and Smootify gives each copy its product.
- **Everything else is optional.** Every attribute of the [product page](https://www.smootify.io/docs/build/product-page) works inside a card: title, prices, conditions, metafields, even a gallery or an add-to-cart form. A card usually needs fewer of them.

```html
<div class="w-dyn-item">
  <smootify-product data-id="{Shopify ID}">
    <a product="link" href="{Current Product}" class="w-inline-block"><img variant="image" src="{Image}" alt="{Image Alt}"></a>
    <a product="url" href="{Current Product}">{Name}</a>
    <smootify-price><div data-prop="price">{Price Formatted}</div></smootify-price>
  </smootify-product>
</div>
```

In purple, the CMS bindings. In a Collection list, link both links to **Current Product** in their link settings, and bind the Image to **Image** and its Alt text to **Image Alt**. The card works before Smootify loads, and search engines read it; Smootify then writes the live values from Shopify.

A few things work differently on a card:

- it ignores `?variant=` in the address, which is only for the page’s own product;
- it starts on the first variant that can be bought, or on the first variant when none can;
- when Shopify does not return the product (it is unpublished, or not sold in the visitor’s market), the Collection item gets the class `w-dyn-bind-empty`.

## What a card can show

### Link and image

Four attributes fill the link and the pictures of a card. The outline shows what each one fills:

| Attribute | Put it on | What it does |
|---|---|---|
| `product="url"` | a Text link | gets the product page's link, and the product title when the link holds only text |
| `product="link"` | a Link block that wraps the image or the whole card | gets the link only |
| `variant="image"` | an Image | shows the image of the card's variant; bound to the CMS `Image` field too, the CMS image shows until the product loads |
| `product="specific-image"` | an Image | shows one image by position: `index="0"` is the first image, `index="1"` the second; without `index`, the second image, such as a picture shown on hover |

Images Smootify writes accept size attributes such as `max-width`, so the card does not download a full-size photo: see [image size](https://www.smootify.io/docs/reference/attributes/image-size).

### Prices

`smootify-price` shows the price of the card’s variant, as on the product page. `variant="unit-price"` adds its price per unit, such as `€1.50/ 100 g` ([Price per unit](https://www.smootify.io/docs/build/product-page#3-price)). For the range of the product’s variants, use these attributes on text. Here on a shirt in four sizes, with S on sale and sold out:

Example: a shirt in four sizes. S costs $29 instead of $39 and is sold out, M costs $32 instead of $45, L costs $35 and XL $39.

| Attribute | What it shows | In the example |
|---|---|---|
| `product="min-price"`, `product="max-price"` | the lowest and highest variant price | $29 and $39 |
| `product="min-available-price"` | the price of the cheapest variant that can be bought | $32 (S is sold out) |
| `product="min-compare-at-price"`, `product="max-compare-at-price"` | the lowest and highest crossed-out price among the variants on sale; hidden when none is on sale | $39 and $45 |
| `condition="variable-price"`, `condition="fixed-price"` | an element shown only when the variants have different prices, or only when they all cost the same | the prices differ: "From" shows |

Every value is in [product values](https://www.smootify.io/docs/reference/attributes/product). With the minimum prices, `condition="variable-price"` gives “From $29 ~~$39~~”:

```html
<div><span condition="variable-price">From </span><span product="min-price">$0.00</span> <span product="min-compare-at-price">$0.00</span></div>
```

### Option values

`product="option-values"` shows the values of one option, such as the colour dots under a card. Smootify makes one copy of the element per value.

```html
<div product="option-values" data-option-name="Color,Colour"><div option="bg-color"></div></div>
```

- Pick the option with `data-option-name="Color,Colour"` (the first of those names the product has, written as in Shopify) or `data-option="2"` (the second option; the default is the first).
- Inside the element, `option="bg-color"` or `option="color"` paints the value’s colour as a background or as the text colour, `option="image"` shows its image and `option="title"` its name. A copy without any of these shows the value’s name as text.
- The colours and images come from Shopify: see [Swatch colours and images](https://www.smootify.io/docs/build/swatches).
- The element is removed when the product does not have that option.
- A link with `product="link"` inside a copy opens the product with that value already chosen (the first available variant with it).
- More in [option values](https://www.smootify.io/docs/reference/attributes/option).

Option values only show. To let the visitor pick a value on the card, use a picker such as [`variant-swatches`](https://www.smootify.io/docs/reference/elements/variant-swatches) inside the card: it changes the card’s variant, its image and its price.

### Badges and labels

- **States.** `condition="on-sale"` (and its opposite `not-on-sale`), `in-stock`, `out-of-stock`, `low-stock` and the other [product conditions](https://www.smootify.io/docs/reference/conditions/product) work on cards. `condition="is-new"` shows a “New” badge while the product was published less than 30 days ago; `data-new-days="14"` on `smootify-product` changes the days.
- **Discount.** `variant="discount-percentage"` shows the discount of the card’s variant. Put it inside an element with `condition="on-sale"`, since it shows `0%` otherwise.
- **Tags.** An element with `tagged="bestseller"` stays only when the product has that tag, and `not-tagged` does the opposite: see [tag conditions](https://www.smootify.io/docs/reference/conditions/product-tags).
- **Collections and vendor.** `product="collections"` makes one copy per collection, with its link on a Text link; `exclude="Home, Sale"` leaves some out. `product="first-collection"` shows one. `product="vendor"` on a Text link links to the [vendor page](https://www.smootify.io/docs/build/vendor-page).

**Hide a card that is already in the cart.** Add `hide-if-product-in-cart` to `smootify-product`. The card (its Collection item) is hidden while a cart line holds the product. It is useful for “You may also like” lists. The value `false` turns it off, in any case, so a component can drive it with a boolean property. In a Webflow Slider, the card’s whole slide is hidden.

**While it loads.** Add `skeleton="text"`, `"image"` or another [loading value](https://www.smootify.io/docs/reference/attributes/loading) to the elements that should shimmer until the product loads.

## Add to cart from a card

There are two ways:

| Way | Built with | Adds | Good to know |
|---|---|---|---|
| A direct button | a Custom element with the tag `button` and `data-is="direct-add-to-cart"`, anywhere inside `smootify-product`; no form | the card's variant, quantity 1 | disabled, with the class `is-disabled`, when the variant cannot be bought; it does not close the cart, so it works on cards inside the cart, such as [cart upsells](https://www.smootify.io/docs/build/cart-upsells) |
| A full form | a `smootify-add-to-cart` with a Form Block inside the card, as on the product page | the quantity, the options and the extra fields of the form | not on cards inside the cart's Form: Webflow does not put a Form Block inside another |

**The direct button** is a Custom element with the tag `button` and the attribute [`data-is="direct-add-to-cart"`](https://www.smootify.io/docs/reference/elements/button-data-is-direct-add-to-cart):

```html
<button data-is="direct-add-to-cart"><div data-state="default">Add to cart</div><div data-state="loading">Adding…</div></button>
```

- The elements with `data-state="default"` and `data-state="loading"` swap while the product is added. Both are optional.
- With a picker on the card, the button adds the variant the visitor picked.

**The full form** is built as in step 6 of [Build the product page](https://www.smootify.io/docs/build/product-page). Use it when the card needs a quantity field, options inside the form or extra fields.

A Webflow Button cannot add to the cart: it publishes a link. Use a Custom element with the tag `button`.

## Cards in a slider

To show the cards of a Collection list in a Webflow Slider, wrap both in a Custom element with the tag [`product-slider`](https://www.smootify.io/docs/reference/elements/product-slider):

```html
<product-slider>
  <!-- Collection list settings. Source: Products -->
  <div class="w-dyn-list">
    <div class="w-dyn-items">
      <div class="w-dyn-item">
        <smootify-product data-id="{Shopify ID}"><a product="url" href="{Current Product}">{Name}</a></smootify-product>
      </div>
    </div>
  </div>
  <div class="w-slider">
    <div class="w-slider-mask"><div class="w-slide"></div></div>
  </div>
</product-slider>
```

Design one empty slide. When the page loads, each Collection item moves into a copy of that slide, and the Collection list is removed. The slide’s own classes and settings stay. For lists that do not come from the CMS, such as related products, see [Product lists without the CMS](https://www.smootify.io/docs/build/product-lists).

## Placeholder cards

A `smootify-product` with `data-id="loader"` is a placeholder card: it loads nothing by itself and is never removed. It is part of the design:

- in search & discovery and on the search page, the first results replace the placeholder cards;
- in a Webflow Slider, a product list such as best sellers fills them in order, one per slide.

Placeholder cards hold the layout while the products load, so the page does not jump. In a Collection list, they also give search engines real content. Where nothing fills them, they stay as designed. More in [`smootify-product[data-id="loader"]`](https://www.smootify.io/docs/reference/elements/smootify-product-data-id-loader).

## Before you publish

> [!STUCK]
> - **Every card shows the same product.** `data-id` holds a fixed id. Bind it to the `cms:Shopify ID` field of the Collection item.
> - **A card is missing or empty.** Shopify did not return that product: it is not published to the sales channel, or not sold in the visitor's market. Its Collection item gets `w-dyn-bind-empty`.
> - **The colour dots show names instead of colours.** The option's values have no swatch in Shopify and no option metaobject. See [Swatch colours and images](https://www.smootify.io/docs/build/swatches).
> - **The add button does nothing.** It is a Webflow Button, which is a link. Use a Custom element with the tag `button` and `data-is="direct-add-to-cart"`.
> - **"0% off" on products that are not on sale.** Put `variant="discount-percentage"` inside an element with `condition="on-sale"`.
> - **Placeholder cards never change.** `data-id="loader"` is only filled inside search & discovery, the search page, or a Webflow Slider with a product list. In a plain Collection list, bind `data-id` to the `cms:Shopify ID` field.
