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 perLISTfacet 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 tofilter-list’sexceptso 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.
Options
Section titled “Options”Attributes on <storesynk-collection>:
| Attribute | Type / default | Meaning |
|---|---|---|
handle | string = "" | Collection handle. Empty lists the whole catalog via a wildcard search query, unless all-handle is set. |
all-handle | string = "" | For the empty-handle case: query this curated all-products collection instead of catalog mode. |
page-size | number = 24 | Products fetched per page. |
sort-key | string = "" | Initial Shopify sort key (e.g. PRICE). |
reverse | boolean | Reverse the initial sort. |
infinite | boolean | Auto-load the next page near the bottom instead of a load-more click. |
no-url-sync | boolean | Opt out of writing state to the URL. |
url-key | string | Namespace this collection’s URL params (a.color=…) so several collections coexist on one page. |
revalidate | boolean | Server-rendered listings only: silently re-check freshness after adoption. |
On the filter tags:
| Tag / attribute | Type / default | Meaning |
|---|---|---|
filter-list except | string | Comma-separated facet keys (id/label/option name) to skip. |
change-filter for | string | The 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. |
Styling
Section titled “Styling”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.
Gotchas
Section titled “Gotchas”- 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 byRELEVANCEandPRICE. If it’s empty, the products aren’t published to your token’s channel. See collections troubleshooting.