Product Cards
What it is / what it's for
A product card is the compact tile that represents a single product in a list — its image, title, price, and an "add" button. You see product cards everywhere shoppers browse multiple products: collection pages, homepage product sliders, search results, "you may also like" recommendations, recently‑viewed, and more.
All of those places use the same card, so a change to the card's look or behaviour updates it consistently across the whole store.
The card can:
- Show a second image on hover, or run a mini image slider on the tile.
- Show sale / sold‑out / new badges.
- Let shoppers pick a variant (e.g. colour) right on the card, with the price and image updating instantly.
- Add to cart directly, or open a quick‑view popup of the product — without leaving the page.
- Display in different styles: vertical, horizontal, overlay (text over the image), or a "creative" shaped card.

Where it lives
| Purpose | File |
|---|---|
| Main card (the "orchestrator") | `snippets/card-product.liquid` |
| Card image area (hover image, slider, badges, in‑image button) | `snippets/card-product-media.liquid` |
| Card text area (reorderable rows) | `snippets/card-product-content.liquid` |
| Overlay‑style action buttons over the image | `snippets/card-product-overlay-chrome.liquid` |
| The add / quick‑view button | `snippets/card-add-button.liquid` |
| Behaviour (instant variant updates) | `assets/product-card.js` (source: `src/scripts/product-card.ts`) |
| Styling | `assets/card-product.css` (source: `src/styles/scss/snippets/card-product.scss`) |
| Behind‑the‑scenes endpoint for variant updates | `sections/product-card-ajax.liquid` |
What it looks like / key parts
A typical card, top to bottom:
- Image area — the product photo, optional hover/second image or slider, and any badges (sale, sold out, new).
- Add / quick‑view button — can sit over the image or below the text, depending on settings.
- Text area — vendor (brand), title, price, optional variant options, rating stars, SKU, stock status, or custom text. These rows can be reordered.
Settings you can change
Where: Product card settings are theme‑wide (they apply to every card in the store). Find them in the Shopify theme editor under Theme settings → Product cards and Theme settings → Product options. (They are not edited per‑section.)
Card appearance
| Setting | What it does |
|---|---|
| **Card style** | Choose the overall layout: *Vertical* (image on top, text below), *Horizontal* (image beside text), *Overlay* (text on top of the image), or *Creative* (a shaped/clipped card). |
| **Padding** | How much space around the card content: *Compact*, *Default*, or *Spacious*. |
| **Image fit** | Whether images *Cover* the frame (cropped to fill) or *Contain* (whole image shown, may letterbox). |
| **Image hover** | What happens on hover — e.g. fade to a second product image. |
| **Media mode** | *Single* image, *Hover* (primary + second image), or *Slider* (a mini carousel of the product's images on the tile). |
| **Slider arrows / pagination** | For slider mode: show arrows and the dots/lines/fraction indicator. |
| **Sold‑out treatment** | How unavailable products look: normal, *grayscale*, or *dimmed*. |
| **Creative shape** | For the creative style: the shape applied to the card. |
The add button
| Setting | What it does |
|---|---|
| **Button position (desktop & mobile)** | Place the add button *inside the image* or *below the content*. Desktop and mobile can differ. |
| **Inside‑media button style** | When the button sits on the image: show it as an icon only, or with text. |
Variant options on the card
| Setting | What it does |
|---|---|
| **Show product options** (`product_options_enable`) | Show variant pickers (e.g. colour/size) directly on the card so shoppers can switch without opening the product. |
| **Visible option labels** (`product_options_visible_labels`) | Instead of showing all options, show only specific ones (e.g. only "Color"). |
| **Picker type / sizes / colours** | Whether options appear as swatches, buttons, or a dropdown, and how they're sized and coloured. |
Tip
If a product has multiple variants but options are not shown on the card, the card automatically uses a quick‑view popup for adding to cart (so the shopper can choose a variant in the popup).
Reorderable text rows (card blocks)
The card's text area is made of blocks you can add, remove, and reorder in the theme editor (within the section that displays the cards). Available rows:
| Row (block) | Shows |
|---|---|
| **Vendor** | The brand / vendor name. |
| **Title** | The product name (links to the product). |
| **Price** | Price, sale price, and "From" price where relevant. |
| **Options** | On‑card variant pickers (swatches/buttons/dropdown). |
| **Add button** | The add‑to‑cart / quick‑view button. |
| **Description** | A short product description (trimmed to a few lines). |
| **SKU** | The product/variant SKU code. |
| **Rating** | Star rating (read from product review metafields). |
| **Inventory** | Stock status message (e.g. "Low stock"). |
| **Custom text** | Free text, or text pulled from a product metafield. |
What it depends on
The card pulls in these shared snippets (documented in [Shared Product Components.md](./Shared%20Product%20Components.md)):
price— the price displayproduct-options(andoption-group,swatch) — variant pickersbadge— sale / sold‑out / new labelsinventory— stock statusproduct-title— the title textcard-add-button→ which renders eitherproduct-form(direct add to cart) orquick-add-modal(quick view)image,placeholder-image,loading-spinner— images and loading states
It loads:
- CSS:
assets/card-product.css - JavaScript:
assets/product-card.js— but only on the real page, not inside the behind‑the‑scenes fragment used for variant updates (this avoids loading the script twice).
How it connects to the rest of the theme
The card is rendered by many sections. The most important:
| Section | Where shoppers see it |
|---|---|
| `sections/main-collection-product-grid.liquid` | The main collection page grid |
| `sections/featured-collection.liquid` | Homepage / page product sliders and grids |
| `sections/main-search.liquid` | Search results |
| `sections/product-recommendations.liquid` | "You may also like" |
| `sections/recently-viewed-products.liquid` | Recently viewed |
| `sections/product-spotlight.liquid`, `favorite-collection.liquid`, `category-product-tabs.liquid` | Various product showcases |
| `snippets/mega-menu-tabbed-columns-slider.liquid` | Product tiles inside the mega‑menu |
When a shopper changes a variant on a card, the card asks the server for an up‑to‑date copy of itself via sections/product-card-ajax.liquid and swaps in the new price, image, stock, and button state.
Product data it uses
- Variants — to show the right price/image and to add the chosen variant to the cart.
- Images / media — main image, optional second (hover) image, or the full set for slider mode; can switch to the selected variant's image.
- Availability & inventory — for sold‑out styling and stock messages.
- Price & compare‑at price — for regular, sale, and "From" pricing.
- Metafields — review rating/score for the star‑rating row, and any metafield used by a "custom text" row.
Customization points & important notes
- Most card appearance is global. Because card settings live in Theme settings → Product cards, changing them updates cards everywhere at once. That's usually what you want; just be aware it's not per‑section.
- Reordering rows is done per the section that displays the cards (its card blocks), so different sections can show different rows.
- Overlay style hides the vendor and uses an action stack over the image; some other settings don't apply to it.
- Editing styles: change
src/styles/scss/snippets/card-product.scss, not the compiledassets/card-product.css(it's overwritten by the build). - Limitation: the card itself doesn't have its own settings panel; it inherits from theme‑wide settings plus the reorderable blocks of the section showing it.
Behind the scenes (for developers)
Orchestration. snippets/card-product.liquid computes display state from theme settings — card-product.liquid:17-103 (padding, sold‑out treatment, image fit, hover mode, multi‑variant detection, card_options_visible, card_show_quick_view, has_card_blocks) and the per‑breakpoint add‑button placement engine (card-product.liquid:105-194). The canonical form id is quick-add-<section_id>-<product.id>[-<card_index>] (card-product.liquid:50-56).
Custom element. The root is <product-card> (card-product.liquid:197-206) carrying data-product-url, data-media-mode, and (when variant images are enabled) data-variant-media-id. Behaviour is in assets/product-card.js (compiled from src/scripts/product-card.ts).
AJAX variant update flow. assets/product-card.js:
- The card listens for the
product-options:changeevent emitted byassets/product-options.js. - On change it fetches
\<productUrl\>?section_id=product-card-ajax&option_values=\<ids\>(Shopify Section Rendering API →sections/product-card-ajax.liquid). Results are cached perproductUrl::ids; an in‑flight request is aborted if a newer change arrives. applyVariantHtml()surgically swaps the live DOM: replacesproduct-price, swaps the primary image children (or navigates the Splide slider to the variant's media viadata-variant-media-id), replaces.card-product__badge, syncs the submit button + hiddenjs-product-form-variant-id, SKU (js-card-sku), inventory (js-card-inventory), togglescard-product--soldout-grayscale/-dim, and dispatchesproduct-card:variant-change.- If the variant combination doesn't exist,
setUnavailable()disables the form and showswindow.variantStrings.unavailable.
AJAX fragment. The fragment is produced by the ajax: true branch of card-product.liquid (the flat, non‑block content layout). That branch deliberately omits the <script> tag (card-product.liquid:372) so the JS isn't re‑injected when the fragment is swapped in.
Endpoint note. The live endpoint is the section sections/product-card-ajax.liquid (section_id=product-card-ajax). The separate file templates/product.product-ajax-do-not-select-or-delete.liquid renders the same card with section_id: 'ajax' and is not the path the card JS calls.
Sub‑snippets:
card-product-media.liquid— image/hover/slider, primary‑image markerjs-card-primary-image, variant image swap, badges, and the in‑media add button instances.card-product-content.liquid— iterates the card blocks in customizer order; rating block readscard_product.metafields.reviews.ratingandreviews.rating_count; custom‑text block reads a metafield or literal text; each block emitsblock.shopify_attributes.card-product-overlay-chrome.liquid— overlay‑style action stack (only for overlay style, non‑horizontal).card-add-button.liquid— quick‑view branch (rendersquick-add-modal, button carriesjs-quick-add-trigger+data-product-url) vs add‑to‑cart branch (rendersproduct-formwithcontext: 'card').
Styling. src/styles/scss/snippets/card-product.scss → assets/card-product.css. Key classes: .card-product (+ --horizontal, --shaped, --soldout-grayscale/-dim), &__media (hover zoom on .group:hover & img), slider chrome (&__media--slider, &__slider-arrow, &__pagination-fraction), &__badge, &__icon, &__content (alignment via --card-text-align/--card-content-align), &__title, &__button, &__rating, &__description.
Naming gotcha: the card markup uses the BEM prefixcard-product__*, while the form/quick‑add controls useproduct-card__*. There is nosnippets/product-card.liquid— the snippet iscard-product.liquid; only the custom element, JS asset, and some classes use theproduct-cardname.