Skip to content

Filtered collection

A server-driven collection page with facets, chips, a result count, and shareable URLs.

A full collection page powered by <storesynk-collection>: a product grid, zero-config checkbox facets with live counts, a custom swatch facet, removable filter chips, and a result count. Shopify filters, sorts, and paginates server-side, and the state mirrors into the URL so filtered views are shareable.

Prerequisite: facets come from the merchant’s Search & Discovery app (Shopify admin → Search & Discovery → Filters), not from the product data. Until a filter is enabled there, its facet section stays hidden.

<storesynk-collection handle="example-collection" page-size="12">
<div class="toolbar">
<p><show-result-count></show-result-count> products</p>
<change-sort>
<select aria-label="Sort by">
<option value="">Featured</option>
<option value="PRICE">Price: low to high</option>
<option value="PRICE:reverse">Price: high to low</option>
<option value="CREATED:reverse">Newest</option>
</select>
</change-sort>
</div>
<!-- Applied-filter chips + clear all. First child is the chip template. -->
<active-filter-list class="chips">
<active-filter-item class="chip">
<show-filter-title></show-filter-title>
<button remove aria-label="Remove filter">×</button>
</active-filter-item>
</active-filter-list>
<clear-filters>Clear all</clear-filters>
<div class="layout">
<aside>
<!-- Custom swatch UX for the Color facet; excluded from the zero-config list below. -->
<change-filter for="color" mode="multi" class="facet">
<h4><show-filter-label></show-filter-label></h4>
<div class="values">
<filter-value>
<show-filter-swatch></show-filter-swatch>
<show-filter-title></show-filter-title>
</filter-value>
</div>
</change-filter>
<!-- Zero-config: one checkbox group per remaining LIST facet, with live counts. -->
<filter-list except="color">
<filter-group class="facet">
<h4><show-filter-label></show-filter-label></h4>
<filter-value class="facet-value">
<label>
<input type="checkbox" />
<show-filter-title></show-filter-title>
(<show-filter-count></show-filter-count>)
</label>
</filter-value>
</filter-group>
</filter-list>
</aside>
<div>
<!-- First child is the card TEMPLATE (cloned per product). -->
<product-list class="grid">
<storesynk-product class="card">
<product-link>
<a>
<show-image main class="card-img"><img alt="" /></show-image>
<span class="card-title"><show-title></show-title></span>
</a>
</product-link>
<show-price class="card-price"></show-price>
<add-to-cart><button type="button">Add to cart</button></add-to-cart>
</storesynk-product>
</product-list>
<load-more>Load more</load-more>
</div>
</div>
</storesynk-collection>
  • <storesynk-collection> owns filter, sort, and cursor state. Every control and the <product-list> grid must sit inside it (SSK-110); outside it they render nothing.
  • <filter-list> renders one checkbox group per LIST facet Shopify returns. Its first <filter-group> is the group template and the <filter-value> inside it the per-value template. Price is always skipped; render it with <change-price>.
  • <change-filter for="color"> gives one facet a custom look. Add that facet’s key to filter-list’s except so it isn’t rendered twice.

Within one filter, selected values combine with OR by default; product tags and list.* metafields can be switched to AND in Search & Discovery. Filters, sort, and page mirror into readable URL params (?color=Blue&price=10-50&sort=PRICE&page=2); add no-url-sync to opt out. With several collections on one page, give each a unique url-key, or URL sync self-disables on the extras.

Attributes on <storesynk-collection>:

AttributeType / defaultMeaning
handlestring = ""Collection handle. Empty lists the whole catalog via a wildcard search query, unless all-handle is set.
all-handlestring = ""For the empty-handle case: query this curated all-products collection instead of catalog mode.
page-sizenumber = 24Products fetched per page.
sort-keystring = ""Initial Shopify sort key (e.g. PRICE).
reversebooleanReverse the initial sort.
infinitebooleanAuto-load the next page near the bottom instead of a load-more click.
no-url-syncbooleanOpt out of writing state to the URL.
url-keystringNamespace this collection’s URL params (a.color=…) so several collections coexist on one page.
revalidatebooleanServer-rendered listings only: silently re-check freshness after adoption.

On the filter tags:

Tag / attributeType / defaultMeaning
filter-list exceptstringComma-separated facet keys (id/label/option name) to skip.
change-filter forstringThe facet to render: option name ("color"), label ("Color"), or id.
change-filter mode"multi" | "single" = "multi"multi toggles values; single replaces within the group, radio-style.
filter-value[ss-active] { font-weight: 600; }
change-price[ss-empty], clear-filters[ss-empty], load-more[ss-empty] { display: none; }
product-list.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 24px; }
product-list[ss-loading] { opacity: .5; }
product-list[ss-empty]::before { content: "No products match."; color: #888; }
show-filter-swatch { width: 24px; height: 24px; border-radius: 50%; display: inline-block; border: 1px solid #ddd; }

The engine paints <show-filter-swatch>’s background from the facet value’s swatch. That needs the filter set to a swatch or image presentation in Search & Discovery.

  • Facet sections are empty but the grid has products: the filters aren’t enabled in Search & Discovery. Each section self-hides via [ss-empty] until its facet exists.
  • <filter-list> needs both templates, a first <filter-group> containing a <filter-value> (SSK-107). <filter-value> and <show-filter-*> only work inside their filter container (SSK-108).
  • Catalog mode (empty handle) only sorts by RELEVANCE and PRICE. If it’s empty, the products aren’t published to your token’s channel. See collections troubleshooting.