Shopify Builds>Three Ships>Collection page: three filter modes, in-grid promo blocks and infinite scroll

Collection Page: Three Filter Modes, In-Grid Promo Blocks and Infinite Scroll

The live collection body here is vendor code from a paid premium theme. This page documents what that code does, how it is configured across a large template set, and where our own contribution to it begins and ends.

Three Ships runs a heavily merchandised collection surface across 46 templates, with a legacy tag taxonomy that Shopify's own facets do not cover. The collection body is Palo Alto vendor code — the filter engine and the promo-tile engine both ship with the theme. Our work on this surface is configuration and extension across that template set, not authorship of the template.

Fact Strip

  • Client: Three Ships
  • Surface: Product listing page
  • Templates served: 13 collection templates carry this section live, out of 46 that reference it
  • Complexity: High
  • Attribution: Built on top of Palo Alto 5.8.0 by Presidio Creative, a paid premium theme. The filter engine and the promo-tile engine ship with that theme. This is configuration and extension work on vendor code, and our contribution inside the file itself is small.
  • Status: Live, verified 2026-09-06
  • Evidence: One code artifact plus two client working records
  • Confidence: Moderate

The Problem

Three Ships runs a large, heavily merchandised collection surface: 46 collection templates covering skin type, skin concern, benefit, price-off tiers and seasonal edits. Different collections need shoppers to narrow the catalog in different ways — a skin-type landing collection and a sale collection are not the same browsing problem, and the taxonomy that separates them lives in product tags predating Shopify's filter metaobjects. Merchandisers also need editorial and offer tiles at specific positions inside the grid, on specific collections, without a developer touching a template between campaigns.

The Constraint

The store runs a purchased theme. The collection body is vendor code that has to keep upgrading, so the honest options are configuration, extension, or a fork we then own forever — and every edit inside that file is a merge conflict later. Shopify's Search & Discovery facets did not cover the legacy tag taxonomy already in use, which is why the vendor section carries three mutually exclusive filter strategies rather than one. And Liquid cannot interleave arbitrary section blocks into a paginated product loop: there is no mutable array to build, sort and read back inside {% paginate %}.

What We Built

The section is collection.liquid, roughly 1,400 lines of vendor Liquid, and it is the live collection body on 13 of the storefront's 46 collection templates. A filter_mode setting selects between three strategies that never run together. default is the mode these templates are configured to; the tag-permalink and grouped-tag modes ship with the theme, and we have not found a template on this storefront that uses either one.

The mode in use, default, reads Shopify's own collection.filters and renders them through the theme's filter snippet with either a price-range slider or fixed price buckets. Filter state is expressed entirely in the URL: selected_tags is rebuilt per link with remove and append, then normalized with a replace on the doubled separator — so filtering works with JavaScript disabled, and every filtered view stays a crawlable path rather than query-string state.

The two modes that are not in use are worth naming only because the theme carries them and a merchandiser can see them in the editor. tag builds a flat list from collection.all_tags and constructs additive tag permalinks — /collections/<handle>/<tag>+<tag>. group reads tags shaped Group_Value, splits them on the underscore into labeled groups, and auto-detects color groups against a nine-language color label list. Both are vendor code, and neither is running here.

The promo mechanism is the theme's answer to Liquid's missing data structures. Section blocks of type promo are captured into one delimited string keyed with sentinel markers for position, HTML, block id, collection handle, width and trigger filter. That string is sorted, then split apart inside the product loop so each tile lands at its grid index with the right desktop, tablet and mobile span. A trigger_filter_label setting lets a promo appear only while a matching filter is active. Pagination is the desktop row width times twelve, with Ajaxinate infinite scroll when the theme setting is on.

What is ours here, and it is not much. Every mechanism described above — the filter engine, the promo-tile engine, the pagination, the empty states — is Presidio Creative's code. The section's own schema settings link out to the vendor's documentation, which is the clearest confirmation of where the line sits. Our contribution on this surface is the configuration layer around that code: which mode each collection template runs, which promo tiles are placed where and on which handles, and the fan-out of that configuration across the template set. We are not claiming authorship of the collection template, and we are not claiming a mechanism inside it as ours.

Why This Way

The theme carries three filter modes because catalogs of this age keep their taxonomy in product tags — phrases like "dry / normal skin", and Group_Value pairs — that predate Shopify's filter metaobjects. What is actually running on these templates is the plainest of the three: Shopify's own facets, configured in the admin. That is the less flattering answer and the correct one: it keeps filtering in a surface the merchandising team can change without a developer, and keeps every filtered view server-rendered on a storefront where skin-concern and benefit collections are entry points from search rather than internal navigation.

The string-sentinel promo array is not elegant and we are not defending it as elegant. It is the standard workaround for Liquid having no mutable array or hash — the only way to collect merchandiser blocks, sort them by position and inject them mid-loop in one pass. The cost of the arrangement is the one we chose deliberately: staying inside vendor code keeps the theme's upgrade path, and it means our reach over this page ends where the vendor's settings end. When a requirement outgrows a filter_mode dropdown, the answer is a conversation about forking, not a quiet edit.

Why Not an App

A collection-filter app is the usual purchase for a catalog with this much merchandising in it, and it would have been a reasonable call. It was not needed, because the theme already ships a filter engine and the taxonomy already exists in tags. The app route would have meant a second index of the same product data, an external script on every product listing page, and filtered views rendered client-side — which on a storefront where filtered collections are organic entry points is the part that costs most. As configured, filtering is native Liquid plus Shopify's own facets: no subscription, no extra bundle, every filtered URL server-rendered.

Implementation Notes

  • Filter markup splits at a threshold of ten values, or twenty for color swatches, into a visible part and a collapsed remainder, with a show-more control that auto-expands whenever a hidden value is active.
  • Tags containing _badge or _preorder are excluded from every filter mode, because they are merchandising flags rather than facets and would otherwise appear to shoppers as filterable attributes.
  • A sticky-bar height is written into :root as a CSS custom property from Liquid, so the no-JavaScript filter rail positions itself correctly before any script has run.
  • Price-range reset labels respect the store's currency-code setting, using a currency-qualified money filter or a trailing-zero-stripped one depending on how the storefront is configured for its markets.
  • Every collection template carrying this section is its own JSON file, so a filter or promo change is repeated per template rather than set once. That fan-out, not any single edit, is the work here.

Edge Cases

  • Three empty states are handled separately: filtered down to zero results, an empty collection that is not the catch-all collection, and theme onboarding placeholders when no collection is bound at all.
  • The sort parameter is preserved across every filter link through a captured prefix that switches between a leading question mark and an ampersand depending on whether a type or vendor query is already present.
  • Tag handles are sanitized for smart quotes before being handleized into URLs, so a tag typed in a word processor still yields a working permalink.
  • Promo tiles can be scoped to a single collection handle, so a shared section group does not leak a seasonal offer tile onto every product listing page on the site.
  • The trailing empty grid slots are computed from the promo widths in play, so the final row does not break when tiles have consumed columns.

Platform Primitives Used

  • Liquid section schema and settings — carries filter_mode, the promo blocks and the infinite-scroll switch, which is the whole configuration surface we work through.
  • Storefront filtering — the default mode's values, counts and price bounds come from Shopify's own facets, configured in the admin rather than in the theme.
  • Liquid pagination{% paginate %} bounds the product loop, and its page size is derived from the configured desktop row width.
  • Theme editor blocks — promo tiles are section blocks, so placement, copy and scope are a merchandiser task between campaigns rather than a deploy.

Integrations in Play

  • Ajaxinate — the infinite-scroll library the theme ships with; it wraps the paginated loop and is enabled or disabled by a theme setting rather than by code.

Where It Runs

One surface, one section, a wide template set. The collection body is live on 13 collection templates spanning skin type, skin concern, benefit, price-off and seasonal edits — each a separate file carrying its own configuration of the same section. That fan-out is the shape of the work.

What This Demonstrates

Primary: Filters, faceted navigation and progressive grid loading, in its configuration form rather than its authored form. It also demonstrates collection merchandising through in-grid promotion, and premium theme extension at scale — the honest middle of build-versus-buy, where a large share of agency delivery sits.

How We Know

One code artifact read directly from the theme — the collection section, roughly 1,400 lines of vendor Liquid — plus two client working records covering a search-bar optimization that implemented filters from source metafields. Those records describe the same filter and promo model, but they do not record who chose the filter mode these templates ship with, or why. Documented from the code; no client-side record of the decision survives. We would rather say that than infer a decision nobody wrote down.

Related Builds

The Buy-vs-Build Question

This page is the case where the answer was neither. The filter engine was already bought, bundled into the theme license, and the work was making it fit a tag taxonomy it was not designed around. That is the most common real position on this question and the least written about. See the filters and faceted navigation decision.

Ready to Get More Out of the Theme You Already Bought?

Most premium themes can do more than they're configured to do, and forking one is a decision worth making on purpose. Contact us today and we'll tell you which side of that line you're on.

More builds