Skip to main content
Horizon collection pages feature advanced filtering, infinite scroll, customizable product grids, and responsive layouts optimized for browsing products.

Key Features

  • Infinite Scroll: Automatically load more products as users scroll
  • Filtering System: Advanced product filtering by tags, variants, price, and more
  • Grid & Editorial Layouts: Choose between classic grid or editorial organic layout
  • Responsive Cards: Adjustable card sizes for mobile and desktop
  • Performance Optimized: Uses view transitions and efficient rendering

Collection Container

The main collection section wraps the product grid and filters:
<results-list
  class="section product-grid-container color-{{ section.settings.color_scheme }}"
  section-id="{{ section.id }}"
  infinite-scroll="{{ section.settings.enable_infinite_scroll }}"
>
  <div class="collection-wrapper grid gap-style">
    {% content_for 'block', type: 'filters', id: 'filters' %}
    
    {% paginate collection.products by products_per_page %}
      {% render 'product-grid',
        section: section,
        children: children,
        products: collection.products,
        paginate: paginate
      %}
    {% endpaginate %}
  </div>
</results-list>

Layout Types

Grid Layout

Classic grid with consistent card sizing:
layout_type: grid

product_card_size:
  options:
    - small
    - medium (default)
    - large
    - extra-large

Editorial Layout

Organic layout with varying card sizes:
layout_type: organic
Features:
  • Variable card sizes for visual interest
  • Masonry-style layout
  • Better for editorial/lifestyle brands

Product Grid Rendering

Paginated Products

{% assign products_per_page = 24 %}
{% if section.settings.enable_infinite_scroll == false %}
  {% assign products_per_page = section.settings.products_per_page %}
{% endif %}

{% paginate collection.products by products_per_page %}
  {% capture children %}
    {% for product in collection.products %}
      <li id="{{ section.id }}-{{ product.id }}"
          class="product-grid__item product-grid__item--{{ forloop.index0 }}"
          data-page="{{ paginate.current_page }}"
          data-product-id="{{ product.id }}"
          ref="cards[]">
        
        {% content_for 'block',
          type: '_product-card',
          id: 'product-card',
          closest.product: product
        %}
      </li>
    {% endfor %}
  {% endcapture %}
  
  {% render 'product-grid',
    section: section,
    children: children,
    products: collection.products,
    paginate: paginate,
    enable_infinite_scroll: section.settings.enable_infinite_scroll
  %}
{% endpaginate %}

Product Card Attributes

id="{{ section.id }}-{{ product.id }}"
Unique identifier for each product card, used for scroll anchoring.

Infinite Scroll

Automatic product loading as users scroll:

Configuration

enable_infinite_scroll:
  type: checkbox
  default: true
When enabled:
  • Products load automatically when scrolling near bottom
  • Uses Intersection Observer API
  • Shows loading indicator
  • Updates URL with current page
When disabled:
  • Shows pagination controls
  • Manual page navigation
  • Configurable products per page

Products Per Page

products_per_page:
  min: 8
  max: 36
  step: 4
  default: 24
  visible_if: enable_infinite_scroll == false
When infinite scroll is enabled, products per page is fixed at 24 for optimal performance.

Card Sizes

Desktop Card Size

product_card_size:
  options:
    - small    # ~250px width
    - medium   # ~350px width (default)
    - large    # ~450px width
    - extra-large # ~600px width
  default: medium
  visible_if: layout_type == 'grid'

Mobile Card Size

mobile_product_card_size:
  options:
    - small  # 2 columns (default)
    - large  # 1 column
  default: small
CSS Implementation:
@media screen and (max-width: 749px) {
  .product-grid[data-mobile-size='small'] {
    grid-template-columns: repeat(2, 1fr);
  }
  
  .product-grid[data-mobile-size='large'] {
    grid-template-columns: 1fr;
  }
}

Grid Layout Settings

Width Options

product_grid_width:
  options:
    - centered (Page width, default)
    - full-width
  default: centered

Full Width on Mobile

full_width_on_mobile:
  type: checkbox
  default: true
  visible_if: product_grid_width != 'full-width'
Forces full-width grid on mobile even when using page-width on desktop.

Gap Settings

columns_gap_horizontal:
  min: 0
  max: 50
  step: 1
  unit: px
  default: 16
Space between product cards horizontally.
CSS Variables:
.product-grid {
  gap: var(--columns-gap-vertical) var(--columns-gap-horizontal);
}

Padding Settings

padding-inline-start:
  min: 0
  max: 100
  step: 1
  unit: px
  default: 0

Filtering System

Integrated filtering block for product discovery:
{% content_for 'block',
  type: 'filters',
  id: 'filters',
  results: collection,
  results_size: collection.products_count
%}
Filter Types:
  • Tags
  • Variants (color, size, etc.)
  • Price range
  • Availability
  • Vendor
  • Product type

URL Hash Navigation

Automatic scroll to products when using URL hash:
const url = new URL(window.location.href);
if (url.hash) {
  document.addEventListener('DOMContentLoaded', () => {
    const card = document.getElementById(url.hash.slice(1));
    if (card) {
      card.scrollIntoView({ behavior: 'instant' });
    }
  }, { once: true });
}
Usage:
/collections/all#product-123
Scrolls directly to product with ID 123.

View Transitions

Smooth animations when emptying cart:
html:active-view-transition-type(empty-cart-page) {
  .cart-items-component {
    view-transition-name: cart-page-content;
  }
}

::view-transition-old(cart-page-content) {
  animation: cart-page-content-old var(--animation-speed-fast) forwards;
}

Grid Column System

The collection wrapper uses a smart grid:
.collection-wrapper {
  display: grid;
  grid-template-columns: var(--grid-column--mobile);
}

@media screen and (min-width: 750px) {
  .collection-wrapper {
    grid-template-columns: var(--grid-column--desktop);
  }
}
With Filters:
--grid-column--mobile: 1fr;
--grid-column--desktop: 250px 1fr; /* Filters | Products */
Without Filters:
--grid-column--mobile: 1fr;
--grid-column--desktop: 1fr;

JavaScript Module

The collection list is powered by results-list.js:
<script src="{{ 'results-list.js' | asset_url }}" type="module" fetchpriority="low"></script>
Handles:
  • Infinite scroll detection
  • URL parameter management
  • Filter state
  • Page transitions
  • Analytics events

Performance Optimizations

Lazy Loading

{{ product.featured_image | image_tag: loading: 'lazy' }}
All product images are lazy-loaded by default.

Fetchpriority

<script src="{{ 'results-list.js' | asset_url }}" fetchpriority="low"></script>
Non-critical JavaScript loads with low priority.

Content Visibility

.product-card {
  content-visibility: auto;
  contain-intrinsic-size: 300px 400px;
}

Accessibility

<results-list role="region" aria-label="{{ 'accessibility.product_list' | t }}">
  <div role="list">
    <div role="listitem">...</div>
  </div>
</results-list>
When loading new products via infinite scroll, focus is managed to prevent disorientation.

Schema Configuration

{
  "name": "Collection Container",
  "enabled_on": {
    "templates": ["collection"]
  },
  "settings": [
    {
      "type": "select",
      "id": "layout_type",
      "options": [
        { "value": "grid", "label": "Grid" },
        { "value": "organic", "label": "Editorial" }
      ],
      "default": "grid"
    },
    {
      "type": "select",
      "id": "product_card_size",
      "options": [
        { "value": "small", "label": "Small" },
        { "value": "medium", "label": "Medium" },
        { "value": "large", "label": "Large" },
        { "value": "extra-large", "label": "Extra Large" }
      ],
      "default": "medium",
      "visible_if": "{{ section.settings.layout_type == 'grid' }}"
    },
    {
      "type": "select",
      "id": "mobile_product_card_size",
      "options": [
        { "value": "small", "label": "Small" },
        { "value": "large", "label": "Large" }
      ],
      "default": "small"
    },
    {
      "type": "checkbox",
      "id": "enable_infinite_scroll",
      "label": "Auto-load products",
      "default": true
    },
    {
      "type": "range",
      "id": "products_per_page",
      "label": "Products per page",
      "min": 8,
      "max": 36,
      "step": 4,
      "default": 24,
      "visible_if": "{{ section.settings.enable_infinite_scroll == false }}"
    },
    {
      "type": "select",
      "id": "product_grid_width",
      "options": [
        { "value": "centered", "label": "Page" },
        { "value": "full-width", "label": "Full" }
      ],
      "default": "centered"
    },
    {
      "type": "checkbox",
      "id": "full_width_on_mobile",
      "label": "Full width on mobile",
      "default": true
    },
    {
      "type": "range",
      "id": "columns_gap_horizontal",
      "label": "Horizontal gap",
      "min": 0,
      "max": 50,
      "step": 1,
      "unit": "px",
      "default": 16
    },
    {
      "type": "range",
      "id": "columns_gap_vertical",
      "label": "Vertical gap",
      "min": 0,
      "max": 50,
      "step": 1,
      "unit": "px",
      "default": 16
    },
    {
      "type": "color_scheme",
      "id": "color_scheme",
      "default": "scheme-1"
    }
  ]
}

Build docs developers (and LLMs) love