Skip to content

Collections & filtering

An optional subsystem that ships in a separate collection.js chunk, loaded on demand the

An optional subsystem that ships in a separate collection.js chunk, loaded on demand the first time a <storesynk-collection> appears on the page (the framework is reused from the main bundle, nothing is duplicated). Unlike storesynk-list (which fetches the whole catalog and filters client-side), this is fully server-driven: Shopify does the filtering, sorting, and cursor pagination and returns facets with live counts, so payload scales with what’s viewed. Filter/sort/page state is mirrored into the URL (shareable, back/forward-restorable). With an empty handle (and no all-handle) it lists the whole catalog via a wildcard search query

  • no “all” collection required (see storesynk-collection attributes below).

The whole region can also be server-rendered (@storesynk/core/server’s renderCollectionListing, via @storesynk/next’s <StoresynkCollection>) and adopted flash-free by the client engine - the grid, facet controls, active-filter chips, result count, and sort selection fill server-side and a data-storesynk-listing payload seeds engine state. See Server-side rendering & adoption below.

<storesynk-collection> provides collectionContext; every control below and the product-list grid consume it and therefore require a storesynk-collection ancestor (SSK-110). The facet leaf parts (show-filter-*, filter-group, filter-value, active-filter-item) do not consume context - their container writes their text - so they are governed by nesting (SSK-108).

  • Purpose: the orchestrator + context provider. Owns filter/sort/cursor state, fetches one page per interaction, mirrors state to the URL, and re-provides collectionContext.
  • Attributes:
    • handle : string = "" - collection handle. Empty lists the whole catalog via a wildcard search query (catalog mode - no “all” collection needs to exist on the store), unless all-handle is set.
    • all-handle : string = "" - opt-in override for the empty-handle case: query this real (curated all-products) collection instead of catalog mode. Default empty = catalog mode. (It was a defaulted "all" fallback before this version; now catalog mode is the default and all-handle is only for when you have a curated collection that sorts/merchandises better.)
    • page-size : number = 24 - products fetched per page.
    • sort-key : string = "" - initial Shopify ProductCollectionSortKeys value (e.g. PRICE). Catalog mode caveat: the wildcard search connection only sorts by RELEVANCE and PRICE; any other change-sort key (or sort-key) falls back to the default order rather than erroring. (A real collection - handle or all-handle - supports all sort keys.)
    • 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. With >1 collection and no url-key, URL sync self-disables.
    • revalidate : boolean (presence) - only meaningful on an adopted (server-rendered) listing. After adopting the SSR payload, the engine silently re-runs the same listing query in the background - no [ss-loading], no dim - and re-provides context only when products/facets/pageInfo actually drifted (unchanged data never repaints; a user-initiated fetch in flight wins via the token guard). It’s the client-side freshness backstop for long-lived (cacheLife("max")) cached renders whose primary invalidation is the Shopify webhook. Same contract as <storesynk-product revalidate>; a no-op on a normal client-fetched (non-adopted) collection.
  • Required ancestor / context: storesynk-store (consumes storeContext); provides collectionContext.
  • Children: any mix of the controls below plus a product-list grid.
  • State it sets: none on itself (loading etc. flow through the context to the controls).
  • Purpose: the product grid. Template-clone container: its first element child is the card template (a storesynk-product, or a wrapper containing one), cloned per product and fed via provideProduct(). Appends only the new tail on load-more; full re-render on filter/sort.
  • Attributes: none. Required ancestor / context: storesynk-collection (SSK-110).
  • State it sets: [ss-loading] while fetching; [ss-empty] when there are no products. (The card storesynk-product carries no handle/product-id - it’s fed by the list.) Under SSR the transform stamps [ss-adopt] on the grid (each card carries its own product payload); the client keeps the server cards, cloning the first (payload-stripped) as its re-render template. If a URL-restored filter/sort/page (or locale) blocks adoption, the server cards stay visible under [ss-loading] until the fetch lands - never a blank flash.
  • Purpose: binds a native <select> to the sort. Option value="" is default; "PRICE", "PRICE:reverse", "CREATED:reverse", etc. (KEY or KEY:reverse). Reflects the active sort (e.g. restored from the URL) back into the dropdown.
  • Attributes: none. Required ancestor / context: storesynk-collection (SSK-110).
  • Children: a <select> with <option>s whose values are the sort keys.
  • Purpose: min/max range control for the PRICE_RANGE facet. Reads the collection’s price bounds and applies an exclusive {price:{min,max}} filter.
  • Attributes: none (it wires child markers: [min-input] / [max-input] on inputs, and [apply] or a <button> to apply - these markers sit on native elements, not storesynk tags).
  • Required ancestor / context: storesynk-collection (SSK-110).
  • State it sets: [ss-empty] when the collection exposes no price facet.
  • Purpose: custom, author-controlled UX for a single facet (swatches, pills, image tiles, label+checkbox - any markup). Clones its first filter-value per value, populates optional show-filter-title / show-filter-count / show-filter-swatch, and toggles [ss-active] on the selected values. Use it for facets you want to style specially; let filter-list handle the rest (and add that facet’s key to filter-list’s except).
  • Attributes:
    • for : string - the facet to render: option name ("color"), label ("Color"), or id.
    • mode : "multi" | "single" - multi (default) toggles values; single replaces within the group (radio-style).
  • Required ancestor / context: storesynk-collection (SSK-110). Template: first filter-value (SSK-107 if missing).
  • State it sets: [ss-empty] when the targeted facet is absent; [ss-active] on active values.
  • Purpose: zero-config facets - renders every LIST facet Shopify returns (with live counts) as a checkbox group. Template-clone: its first filter-group is the group template and the filter-value inside it is the per-value template. Price is always skipped (use change-price).
  • Attributes: except : string - comma-separated facet keys (id/label/option name) to skip (render those with a custom change-filter).
  • Required ancestor / context: storesynk-collection (SSK-110). Template: a filter-group containing a filter-value (SSK-107 if missing).
  • State it sets: [ss-empty] when there are no facets.
  • Purpose: the per-facet group template inside filter-list (holds a show-filter-label and the filter-value template). Registered styleable.
  • Attributes: none. Required ancestor: filter-list (SSK-108).
  • Purpose: the per-value template - one facet value (a checkbox row, a swatch, a pill). The engine sets its text via the inner show-filter-* and toggles [ss-active]; a native <input> inside is kept checked, else the value itself is made clickable.
  • Attributes: none. Required ancestor: filter-list or change-filter (SSK-108).
  • Children: optionally show-filter-title, show-filter-count, show-filter-swatch, an <input>.
  • Purpose: removable chips for the applied filters. Template-clone: its first element child is the chip template (typically active-filter-item), cloned per active value. Clicking the chip’s [remove]/<button>/<a> (or the chip) removes that filter.
  • Attributes: none. Required ancestor / context: storesynk-collection (SSK-110). Template: a first element child (SSK-107 if missing).
  • State it sets: [ss-empty] when no filters are applied.
  • Purpose: the chip template inside active-filter-list (holds a show-filter-title and a remove control). Registered styleable.
  • Attributes: none. Required ancestor: active-filter-list (SSK-108).
  • Purpose: clears all applied filters. Keyboard-operable (made clickable, aria-label “Clear all filters”).
  • Attributes: none. Required ancestor / context: storesynk-collection (SSK-110).
  • State it sets: [ss-empty] when no filters are applied (hide it with CSS).
  • Purpose: fetches the next page. Keyboard-operable (made clickable, aria-label “Load more products”).
  • Attributes: none. Required ancestor / context: storesynk-collection (SSK-110).
  • State it sets: [ss-empty] when there are no more pages; [ss-loading] while fetching. (With infinite on storesynk-collection you usually omit this.)
  • Purpose: writes the number of products currently loaded into its textContent.
  • Attributes: none. Required ancestor / context: storesynk-collection (SSK-110).

show-filter-label / show-filter-title / show-filter-count

Section titled “show-filter-label / show-filter-title / show-filter-count”
  • Purpose: invisible (display:contents) text holders the engine fills - the facet label (group name), a value’s title, and a value’s live count. Registered invisible.
  • display:contents is applied only when the element is UNSTYLED (no class, no inline display). This applies to every invisible-registered part (the show-filter-*, show-option-title, show-locale-*, show-selling-plan-* holders). Give one an authored class
    • e.g. a Tailwind .sr-only to keep a swatch’s color name available to screen readers - and the author owns its rendering: the engine leaves display:contents off, because it would dissolve the element’s box and defeat box-based hiding like .sr-only’s 1px/clip.
  • Attributes: none. Required ancestor: a filter container (SSK-108) - show-filter-label in a filter-list group or a change-filter; show-filter-title in a filter-value or an active-filter-item chip; show-filter-count in a filter-value.
  • Purpose: swatch visual inside a change-filter’s filter-value; the engine paints its background-color (hex) or background-image (URL) from the facet value’s swatch/image (merchant’s Search & Discovery presentation). Registered styleable.
  • Attributes: none. Required ancestor: change-filter (in a filter-value) (SSK-108).

Imported from the Storesynk skill v0.1.0 (references/component-reference.md). To change this page, change it there.