Collection selection and comparison
Define real distinctions before implementing filters, results and model comparison.
Real attribute filters and empty results
Generate controls from actual collection filter values, parameter names and counts. Preserve selections and validate apply/reset behavior before adding a mobile drawer.
Data and dependencies
- Configured list/boolean filters.
- Price ranges require their own min/max controls.
Interaction logic
- Disable unselected values with no results.
- Keep selected zero-result values removable and retain sorting.
Example code · snippets/ww-filters.liquid
<form action="{{ collection.url }}" method="get">
{% for filter in collection.filters %}
{% if filter.type == 'list' or filter.type == 'boolean' %}
<fieldset>
<legend>{{ filter.label | escape }}</legend>
{% for value in filter.values %}
<label>
<input type="checkbox" name="{{ value.param_name }}" value="{{ value.value | escape }}"
{% if value.active %}checked{% endif %}
{% if value.count == 0 and value.active == false %}disabled{% endif %}>
{{ value.label | escape }} ({{ value.count }})
</label>
{% endfor %}
</fieldset>
{% endif %}
{% endfor %}
<input type="hidden" name="sort_by" value="{{ collection.sort_by | default: collection.default_sort_by }}">
<button type="submit">Apply filters</button>
<a href="{{ collection.url | escape }}">Clear all filters</a>
</form>
<p>{{ collection.products_count }} products</p>
Acceptance checks
- Test positive, empty, combined and reset cases.
- After reload, compare parameters, selections and results.
The fragment excludes price-range controls. Determine faceted-URL crawling separately; hiding parameters in the interface is insufficient.
Official documentation
Implementation references
Product grids, sorting and pagination
Keep the collection introduction and render filtered results through one card. Native pagination keeps results reachable. Sorting should retain filters and operate beyond the current screen.
Data and dependencies
- collection.products and the product-card dependency.
- Use collection.sort_options and submit with current filters.
Interaction logic
- Provide a recovery route when no items match.
- Render the correct products and usable pagination.
Example code · snippets/ww-collection-grid.liquid
<h1>{{ collection.title | escape }}</h1>
<div>{{ collection.description }}</div>
{% paginate collection.products by 24 %}
<ul class="ww-product-grid">
{% for product in collection.products %}
<li>{% render 'ww-product-card', product: product %}</li>
{% else %}
<li>No matching products. Change or clear the filters.</li>
{% endfor %}
</ul>
{{ paginate | default_pagination }}
{% endpaginate %}
Example code · snippets/ww-product-card.liquid
{% assign card_product = product %}
{% assign card_variant = card_product.selected_or_first_available_variant %}
{% assign card_form_id = 'CardProduct-' | append: section.id | append: '-' | append: card_product.id %}
<article>
<a href="{{ card_product.url | escape }}">
{% if card_product.featured_image %}
{{ card_product.featured_image | image_url: width: 640 | image_tag: widths: '320, 480, 640', sizes: '(max-width: 749px) 45vw, 25vw', loading: 'lazy' }}
{% endif %}
<h3>{{ card_product.title | escape }}</h3>
</a>
<p>{{ card_product.metafields.custom.key_spec.value | escape }}</p>
<p>{{ card_variant.price | money_with_currency }}</p>
{% if card_product.has_only_default_variant and card_variant.available %}
{% form 'product', card_product, id: card_form_id %}
<input type="hidden" name="id" value="{{ card_variant.id }}">
<button type="submit">Add to cart</button>
{% endform %}
{% else %}
<a href="{{ card_product.url | escape }}">Choose options and check availability</a>
{% endif %}
</article>
Example code · assets/ww-module-layout.css
.ww-product-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 1.25rem;
list-style: none;
padding: 0;
}
.ww-product-grid img { width: 100%; height: auto; }
.ww-product-grid h3 { overflow-wrap: anywhere; }
.ww-table-scroll { max-width: 100%; overflow-x: auto; }
.ww-table-scroll table { border-collapse: collapse; min-width: 30rem; }
.ww-table-scroll th, .ww-table-scroll td { padding: .75rem; text-align: start; }
@media (max-width: 749px) {
.ww-product-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .75rem; }
}
Acceptance checks
- Navigate to the last page and back.
- Check sorting/filter combinations and card prices.
The example depends on ww-product-card. Use the theme grid and integrate sorting into the filter form.
Official documentation
Implementation references
WESWOO guides
Model differences and an HTML comparison
Choose dimensions from real use and compatibility questions, keep units and test conditions comparable, and link each model to its product page.
Data and dependencies
- collection.custom.comparison_products: list.product_reference.
- Keep product custom.key_spec consistent with source specifications.
Interaction logic
- Show missing data as not supplied rather than zero.
- Allow focused horizontal reading with table headers.
Example code · snippets/ww-comparison.liquid
{% assign models = collection.metafields.custom.comparison_products.value %}
{% if models != blank %}
<div class="ww-table-scroll" role="region" aria-label="Model comparison" tabindex="0">
<table>
<caption>Compare the models in this collection</caption>
<thead><tr><th scope="col">Model</th><th scope="col">Use and key specification</th></tr></thead>
<tbody>
{% for model in models %}
<tr>
<th scope="row"><a href="{{ model.url | escape }}">{{ model.title | escape }}</a></th>
<td>{{ model.metafields.custom.key_spec.value | default: 'Not supplied' | escape }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
Example code · assets/ww-module-layout.css
.ww-product-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 1.25rem;
list-style: none;
padding: 0;
}
.ww-product-grid img { width: 100%; height: auto; }
.ww-product-grid h3 { overflow-wrap: anywhere; }
.ww-table-scroll { max-width: 100%; overflow-x: auto; }
.ww-table-scroll table { border-collapse: collapse; min-width: 30rem; }
.ww-table-scroll th, .ww-table-scroll td { padding: .75rem; text-align: start; }
@media (max-width: 749px) {
.ww-product-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .75rem; }
}
Acceptance checks
- Follow a real selection question to a model.
- Check units, conditions and product links.
The example compares one dimension. Expand columns from the same field dictionary when needed.
Read it, integrate it, verify it
- Choose a module and confirm the data contract.
- Connect the fragment to the current theme or framework.
- Check real variants, markets and recovery states.
Examples target Shopify Online Store 2.0 themes. Headless has separate data and version boundaries. Integrate fragments with actual fields, theme components and locale files.
Architecture references
Original educational examples with Shopify documentation and pinned upstream references. Follow the applicable license when adopting repository code.
References checked 2026-11-12