Production Shopify · Collection grids

Build collection-driven product grids merchants control

Product grids on landing pages, collection pages, and homepage features are not hardcoded product cards. This guide is about grids that render whichever products the merchant places in a collection—no theme edits when inventory changes, no handle arrays to maintain, no emergency developer calls when a SKU sells out.

Production implementation guide · Updated 2026-08-03

Collection-driven grids vs hardcoded product handles

Hardcoding product handles in a section works for a static 'As Seen In' lockup that never changes. For merchandising grids—homepage bestsellers, seasonal featured products, category landing showcases—the merchant must choose products by managing a collection. The section reads collection.products in a loop. No code changes when inventory or strategy shifts.

Collection-driven grids

  • Merchant updates collection membership in product admin or collection conditions
  • Section renders whatever products are in that collection at request time
  • Product order controlled by collection sort (manual, best-selling, price)
  • One section works across all merchandising surfaces—no handle maintenance

Hardcoded product handles

  • Developer edits handle array in section Liquid or settings every time
  • Out-of-stock handles leave broken cards or require manual cleanup
  • Manual sort order locked in code, not accessible to merchandising
  • Duplicate sections proliferate when each landing page needs different products

Section settings: picking the collection to render

Use a collection setting so the merchant chooses which collection the grid pulls from. This lets one section serve 'Bestsellers' on the homepage, 'New Arrivals' on a landing page, and 'Sale' on another—all controlled in the theme editor without duplicating section code.

Provide a sensible default or a clear empty state when no collection is selected. Do not assume the 'featured' collection exists in every theme.

Collection setting and loop sketch

{% comment %} Schema excerpt {% endcomment %}
{
  "settings": [
    {
      "type": "collection",
      "id": "collection",
      "label": "Collection to display"
    },
    {
      "type": "range",
      "id": "products_to_show",
      "min": 2,
      "max": 12,
      "step": 1,
      "default": 4,
      "label": "Number of products"
    }
  ]
}

{% comment %} Liquid logic {% endcomment %}
{% if section.settings.collection != blank %}
  <div class="product-grid">
    {% for product in section.settings.collection.products limit: section.settings.products_to_show %}
      <div class="product-card">
        <a href="{{ product.url }}">
          <img 
            src="{{ product.featured_image | image_url: width: 600 }}" 
            alt="{{ product.featured_image.alt | escape }}"
            loading="lazy"
            width="600"
            height="{{ 600 | divided_by: product.featured_image.aspect_ratio | round }}"
          >
          <h3>{{ product.title | escape }}</h3>
          <p>{{ product.price | money }}</p>
        </a>
      </div>
    {% endfor %}
  </div>
{% else %}
  <p class="product-grid__empty">Choose a collection in the theme editor to display products here.</p>
{% endif %}

Collection picker setting exposes merchant control. Limit avoids pagination complexity on simple grids. Empty state guides setup.

Product loop and limiting results without breaking pagination

Use {% for product in collection.products limit: N %} to control how many products render. Be aware that collections paginate at 50 by default—if you need more, consider whether your grid should link to the full collection page instead of trying to render hundreds of cards.

Do not pull all products into a section grid and rely on CSS to hide overflow. Performance and SEO suffer when 200 product cards render but only 8 are visible.

Limit products cleanly in the loop

{% assign products_shown = 0 %}
{% for product in section.settings.collection.products %}
  {% if products_shown >= section.settings.products_to_show %}
    {% break %}
  {% endif %}
  
  <div class="product-card">
    <a href="{{ product.url }}">
      <img 
        src="{{ product.featured_image | image_url: width: 600 }}" 
        alt="{{ product.featured_image.alt | escape }}"
        loading="lazy"
        width="600"
        height="{{ 600 | divided_by: product.featured_image.aspect_ratio | round }}"
      >
      <h3>{{ product.title | escape }}</h3>
      <p>{{ product.price | money }}</p>
      {% if product.compare_at_price > product.price %}
        <p class="product-card__compare">
          <s>{{ product.compare_at_price | money }}</s>
        </p>
      {% endif %}
    </a>
  </div>
  
  {% assign products_shown = products_shown | plus: 1 %}
{% endfor %}

{% if products_shown == 0 %}
  <p class="product-grid__empty">This collection has no available products.</p>
{% endif %}

Loop stops after the limit. No hidden DOM bloat. Empty state when collection exists but is empty.

image_url filter: serve responsive product images

Use {{ product.featured_image | image_url: width: N }} to request appropriately sized product images. Shopify's CDN resizes on demand. Do not use the original full-resolution image URL for a 300px card—performance and Core Web Vitals will suffer.

Provide width and calculated height attributes on img elements so browsers can reserve space and avoid layout shift.

Responsive image with aspect ratio and sizes

{% assign card_width = 600 %}
<img 
  src="{{ product.featured_image | image_url: width: card_width }}" 
  srcset="
    {{ product.featured_image | image_url: width: 300 }} 300w,
    {{ product.featured_image | image_url: width: 600 }} 600w,
    {{ product.featured_image | image_url: width: 900 }} 900w
  "
  sizes="(min-width: 1024px) 25vw, (min-width: 768px) 33vw, 50vw"
  alt="{{ product.featured_image.alt | default: product.title | escape }}"
  loading="lazy"
  width="{{ card_width }}"
  height="{{ card_width | divided_by: product.featured_image.aspect_ratio | round }}"
>

Srcset gives browser options. Sizes hint matches grid layout. Width and height prevent CLS.

Blank states: when the collection is empty or not selected

An empty collection (zero published products) and an unselected collection (merchant has not picked one yet) are different states. Provide helpful messaging for both. Do not render broken card chrome or leave a mysterious empty container.

Empty states should guide the merchant to the next action—choose a collection, or add products to the selected collection.

Figure — blank state UX checkpoints
  • No collection selected: 'Choose a collection in the theme editor to display products here.'
  • Collection selected but empty: 'This collection has no available products. Add products to [Collection Name] in your admin.'
  • Collection not found (deleted): 'The selected collection could not be found. Please choose another collection.'
  • All products out of stock but collection not empty: Decide if section should hide or show unavailable cards.

Blank states are merchant-facing UI. Write for store operators, not developers.

When NOT to use collection-driven grids

Some product showcases should be hardcoded: a 'Featured in Vogue' lockup with 3 specific SKUs that never change, a founder's story with 2 hero products in specific positions, or an editorial layout where product order is part of the design narrative. Use handles or product picker settings for those. Collection-driven grids are for merchandising surfaces where membership and order change regularly.

Do not force every product display into a collection paradigm. Choose the right model for the business process.

Production debugging for collection product grids

Product grid symptoms and checks
SymptomChecks
Grid shows no productsCollection selected? Collection empty? Products unpublished? Wrong sales channel?
Grid shows fewer products than limit settingOut-of-stock products hidden by theme? Unpublished products? Collection conditions exclude some?
Wrong products appear in gridCollection conditions changed? Wrong collection selected in editor? Cached collection object?
Images broken or slowimage_url filter used? Width appropriate for card size? Srcset provided?

Practice a collection product grid by hand

Build this on a development homepage or landing page before using any generator. You should be able to explain why collection-driven is better than hardcoded handles for merchandising, and how to handle blank states.

  1. 01

    Create sections/collection-product-grid.liquid

    Collection setting and products_to_show range. Do not use blocks unless you need per-product custom overrides.

  2. 02

    Loop over collection.products with limit

    Use limit to control count. Do not render all and hide with CSS.

  3. 03

    Product card markup with image_url filter

    Responsive image with srcset, sizes, width, height. Product title, price, link.

  4. 04

    Blank state handling

    Message when no collection selected. Message when collection is empty. Avoid silent empty container.

  5. 05

    Test with real merchandising workflow

    Create a collection, add products, assign to section, reorder in admin, verify grid updates without theme edits.

Use the converter after the collection architecture is decided

If you have static product card HTML from a design comp, the converter can draft the Liquid loop faster once you know you need collection-driven rendering and blank-state handling. The converter does not decide merchandising architecture for you.

Supporting draft acceleration—not a substitute for collection grid design decisions.

What to study next for collection-driven sections

Continue with adjacent skills. Campaign heroes and FAQ sections are different information architectures—study them when you need those outcomes, not product grids.

Beginner

Understand collections and Liquid loops before grids.

Intermediate

Settings, filters, and responsive images for grids.

Advanced

Scale product grids across themes and templates.

Editorial review

Reviewed by the HTML to Liquid Converter team

Dhruv Goyani, Shopify Developer at HTML to Liquid Converter

Dhruv Goyani

3 years web design + 3 years Shopify development experience

LinkedIn profile →
Nishad Kikani, Lead Shopify Developer at HTML to Liquid Converter

Nishad Kikani

2 years web design + 6 years Shopify development experience

LinkedIn profile →

Content is reviewed by the HTML to Liquid Converter Shopify development team before publication. Technical accuracy is validated against current Shopify Online Store 2.0 conventions and active client theme work.

Last updated:

Questions or corrections? Contact us.