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-collectionattributes 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).
storesynk-collection
Section titled “storesynk-collection”- 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 wildcardsearchquery (catalog mode - no “all” collection needs to exist on the store), unlessall-handleis set.all-handle : string = ""- opt-in override for the empty-handlecase: 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 andall-handleis only for when you have a curated collection that sorts/merchandises better.)page-size : number = 24- products fetched per page.sort-key : string = ""- initial ShopifyProductCollectionSortKeysvalue (e.g.PRICE). Catalog mode caveat: the wildcardsearchconnection only sorts byRELEVANCEandPRICE; any otherchange-sortkey (orsort-key) falls back to the default order rather than erroring. (A real collection -handleorall-handle- supports all sort keys.)reverse : boolean- reverse the initial sort.infinite : boolean- auto-load the next page near the bottom (instead of aload-moreclick).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 nourl-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(consumesstoreContext); providescollectionContext. - Children: any mix of the controls below plus a
product-listgrid. - State it sets: none on itself (loading etc. flow through the context to the controls).
product-list
Section titled “product-list”- 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 viaprovideProduct(). 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 cardstoresynk-productcarries nohandle/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.
change-sort
Section titled “change-sort”- Purpose: binds a native
<select>to the sort. Optionvalue=""is default;"PRICE","PRICE:reverse","CREATED:reverse", etc. (KEYorKEY: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.
change-price
Section titled “change-price”- Purpose: min/max range control for the
PRICE_RANGEfacet. 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.
change-filter
Section titled “change-filter”- Purpose: custom, author-controlled UX for a single facet (swatches, pills, image tiles,
label+checkbox - any markup). Clones its first
filter-valueper value, populates optionalshow-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; letfilter-listhandle the rest (and add that facet’s key tofilter-list’sexcept). - Attributes:
for : string- the facet to render: option name ("color"), label ("Color"), or id.mode : "multi" | "single"-multi(default) toggles values;singlereplaces within the group (radio-style).
- Required ancestor / context:
storesynk-collection(SSK-110). Template: firstfilter-value(SSK-107 if missing). - State it sets:
[ss-empty]when the targeted facet is absent;[ss-active]on active values.
filter-list
Section titled “filter-list”- Purpose: zero-config facets - renders every
LISTfacet Shopify returns (with live counts) as a checkbox group. Template-clone: its firstfilter-groupis the group template and thefilter-valueinside it is the per-value template. Price is always skipped (usechange-price). - Attributes:
except : string- comma-separated facet keys (id/label/option name) to skip (render those with a customchange-filter). - Required ancestor / context:
storesynk-collection(SSK-110). Template: afilter-groupcontaining afilter-value(SSK-107 if missing). - State it sets:
[ss-empty]when there are no facets.
filter-group
Section titled “filter-group”- Purpose: the per-facet group template inside
filter-list(holds ashow-filter-labeland thefilter-valuetemplate). Registeredstyleable. - Attributes: none. Required ancestor:
filter-list(SSK-108).
filter-value
Section titled “filter-value”- 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 keptchecked, else the value itself is made clickable. - Attributes: none. Required ancestor:
filter-listorchange-filter(SSK-108). - Children: optionally
show-filter-title,show-filter-count,show-filter-swatch, an<input>.
active-filter-list
Section titled “active-filter-list”- 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.
active-filter-item
Section titled “active-filter-item”- Purpose: the chip template inside
active-filter-list(holds ashow-filter-titleand a remove control). Registeredstyleable. - Attributes: none. Required ancestor:
active-filter-list(SSK-108).
clear-filters
Section titled “clear-filters”- 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).
load-more
Section titled “load-more”- 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. (Withinfiniteonstoresynk-collectionyou usually omit this.)
show-result-count
Section titled “show-result-count”- 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. Registeredinvisible. display:contentsis applied only when the element is UNSTYLED (noclass, no inlinedisplay). This applies to everyinvisible-registered part (theshow-filter-*,show-option-title,show-locale-*,show-selling-plan-*holders). Give one an authored class- e.g. a Tailwind
.sr-onlyto keep a swatch’s color name available to screen readers - and the author owns its rendering: the engine leavesdisplay:contentsoff, because it would dissolve the element’s box and defeat box-based hiding like.sr-only’s1px/clip.
- e.g. a Tailwind
- Attributes: none. Required ancestor: a filter container (SSK-108) -
show-filter-labelin afilter-listgroup or achange-filter;show-filter-titlein afilter-valueor anactive-filter-itemchip;show-filter-countin afilter-value.
show-filter-swatch
Section titled “show-filter-swatch”- Purpose: swatch visual inside a
change-filter’sfilter-value; the engine paints itsbackground-color(hex) orbackground-image(URL) from the facet value’s swatch/image (merchant’s Search & Discovery presentation). Registeredstyleable. - Attributes: none. Required ancestor:
change-filter(in afilter-value) (SSK-108).
Imported from the Storesynk skill v0.1.0 (references/component-reference.md). To change this page, change it there.