Shopify Builds>LUS Brands>Shop-by-category carousel with collection-aware card fallbacks

Shop-by-Category Carousel With Collection-Aware Card Fallbacks

A homepage category rail where adding a category is one action — pick the collection. The image, the link and the label all resolve from the collection record unless a merchandiser deliberately overrides them.

Every category tile row on a homepage asks a merchandiser to re-upload artwork and retype a label that already exists on the collection. This section resolves each card's link, label and image through a three-step fallback chain — block setting, then the collection's own record, then a Shopify placeholder — so adding a category is picking a collection, and overriding one is optional.

Fact Strip

  • Client: LUS Brands — haircare DTC, loveurcurls.com
  • Surfaces: Home page
  • Templates served: One — the index template
  • Complexity: Medium — roughly 500 lines across the section and its script
  • Attribution: Deploi-authored. The section, its script and its styles are ours. The theme they sit in is built on Dawn 15.4.1, and the section stands Dawn's own section spacing down rather than fighting it.
  • Status: Live — verified 2026-09-06
  • Evidence: Strong — read from the theme and corroborated by client working-session records
  • Platform primitives: 4

The Problem

The homepage needed a browsable entry point into the catalogue's category structure — one that a merchandiser could re-order per campaign without a developer. The usual way to build that is a row of tiles, each with an uploaded image, a typed label and a pasted URL. Which means the same three facts now live in two places: on the collection record, where Shopify already keeps a title, a URL and a featured image, and in the section's block settings, where somebody retyped them. The two copies agree on the day they are created. They stop agreeing the first time a collection is renamed, and nothing on the homepage notices.

The Constraint

Theme-editor authorable was the hard requirement, and four more sat behind it. The row had to peek to the next card on mobile — the sliver of the following tile is the affordance that tells a shopper the row scrolls, and without it a carousel reads as a cropped grid. It could not put a second carousel library into a theme that already loads one. It had to survive theme-editor section reload, because the editor tears a section out and re-inserts it on every change, and a slider initialized once at page load is dead for the rest of that session.

And the cards had to work in two interaction modes — a whole-card link, or a link on the label only. A stretched anchor over the whole tile is the better target, but it cannot contain other interactive content, so any campaign wanting a second link or a button on a tile needs the other mode to exist already.

What We Built

de_shop_by_category.liquid renders a repeatable category block, and each card resolves three values through its own fallback chain in Liquid. The link is block.settings.link, else collection.url, else #. The label is block.settings.bottom_text, else collection.title. The image is block.settings.image, else collection.featured_image, else a Shopify placeholder_svg. A merchandiser who picks a collection and stops has a working card; a merchandiser who wants campaign artwork and a campaign label sets those two and inherits the rest.

A make_card_clickable toggle switches the tile between its two modes. On, and the card gets a stretched .if-cat__slide-link overlay anchor carrying an aria-label. Off, and a plain anchor wraps the label alone, leaving the rest of the tile free for whatever else the campaign needs in it.

The settings schema carries the rest of the vocabulary: a three-part heading with an accent word, a richtext subheading, an accent color piped through as the --if-cat-accent custom property, an authorable tablet breakpoint clamped in Liquid at at_most: 940, a page-width-or-bleed switch, four independent desktop and mobile padding ranges, an empty-state message, and separate aria labels for the carousel and its pagination.

de_shop_by_category.js builds a breakpoint config of 1.3, 2.3 and 4 slides per view. The fractional values are the peek — 1.3 slides means one full card and a third of the next one, which is the whole mobile affordance expressed as a number. Pagination is made clickable only when there is more than one slide. The script binds shopify:section:load and shopify:section:unload, with a destroy path on the unload side, so a merchandiser dragging the section around the theme editor gets a working carousel every time rather than a working one and then a dead one.

Blocks with neither a collection nor an image are filtered out before render and excluded from the slide count, so a half-configured block never becomes a blank slide with a live pagination dot under it.

Why This Way

The fallback chain is the whole argument. It moves the source of truth for a homepage tile onto the collection record, where a merchandiser is already maintaining it, and reduces "add a category to the homepage" to one decision instead of four.

That has a cost, and it is worth being precise about it. Renaming a collection now renames a homepage label. Most of the time that is exactly right — it's why the chain exists. It is wrong on the day someone renames a collection for a catalogue reason and the homepage quietly follows. The per-block overrides are the escape hatch, but they only help if a merchandiser knows to reach for them, which is a documentation problem the code cannot solve.

The tablet breakpoint is a setting rather than a constant for a related reason: card artwork changes per campaign, and the column count that looks right changes with it. The second cost sits there. Three of these settings are shape controls, and a panel that can express a layout can also express a bad one.

Why Not an App

This is the classic "category slider" sold inside paid section libraries — buy the pack, drop the section in, configure it in the editor. Two things follow when you do. The section brings its own carousel library, so the theme is now running two of them, and the one the shopper waits for is the one that arrived last. And the cards belong to the pack: the pack's markup, the pack's class names, the pack's settings vocabulary, none of which know anything about the theme's color tokens or its spacing scale.

Building it in the theme keeps the row on the carousel library the theme already loads and on the theme's own custom properties, so a re-theme reaches this section without a second stylesheet to remember. It also keeps the fallback chain possible at all — a section pack cannot read your collection records to fill in a card, because it was written for stores whose collections it has never seen.

Implementation Notes

  • The first four slides load eager and the rest lazily, matched to the widest breakpoint's four-slides-per-view — so the eager set is exactly what a desktop shopper sees before they scroll, and not one image more.
  • The responsive sizes attribute is authored per breakpoint — (min-width: 990px) 22vw, (min-width: 750px) 35vw, 45vw — so the browser picks a candidate against the column count the card will actually sit in.
  • Alt text chains through image.alt, then the card's own label, then the shop name, so a category tile is never an unlabelled image even when the artwork arrives without alt text.
  • The section neutralizes Dawn's default section margins with an #shopify-section-{{ id }} rule, which is what lets the carousel sit flush against the sections above and below it instead of inside Dawn's spacing.
  • The accent color is written into --if-cat-accent from a color setting, so the heading's accent word and the pagination read from one source rather than two hard-coded hexes.
  • The polling, init and destroy skeleton and the if- CSS prefix are a house pattern reused across this theme's custom sections rather than written fresh for this one.

Edge Cases

  • Blocks with neither a collection nor an image are filtered out before render and excluded from the slide count, so an unfinished block never produces a blank slide.
  • Zero renderable blocks renders the authored empty-state message and no carousel at all, rather than an empty rail with live controls above it.
  • A single slide renders with no pagination element rather than a one-dot pager that looks like a control and does nothing.
  • A missing image falls back to Shopify's placeholder_svg_tag rather than an empty box on the homepage.
  • An authored tablet breakpoint below 400 is corrected to 750 in the script, and one above 940 is clamped in Liquid. Both ends of a free-text number setting are defended.
  • Clickable-card mode gives the overlay anchor an aria-label, so the stretched link is not announced as an unlabelled link.

Platform Primitives Used

  • Liquid section schema and settings — the settings carry the whole vocabulary: heading parts, accent color, tablet breakpoint, four padding ranges, empty state, aria labels.
  • Theme blocks — one category block per card, so re-ordering the row per campaign is a drag in the editor.
  • Responsive images — the sizes string is authored per breakpoint to match the slides-per-view at that breakpoint rather than a generic viewport fraction.
  • CSS custom properties--if-cat-accent is written from a setting, so a second placement of the same section re-themes without touching the stylesheet.

Integrations in Play

  • Swiper — the row runs on the carousel library the theme already loads rather than bringing a second copy of it along.

Where It Runs

One template — index — one section, one script, one stylesheet. It sits on the home page as the catalogue's category entry point, and the page-width-or-bleed setting decides whether it lives inside the theme's own container or breaks out past it to the full viewport width.

What This Demonstrates

A collection merchandising build, and specifically the half of that capability which is about data rather than layout: a merchandiser-ordered rail whose cards read the catalogue instead of keeping a second copy of it.

How We Know

The section, its script and its built counterpart, read directly from the live theme, plus three client working-session records covering the homepage category-tile work this row came out of. Confidence is strong on that basis. One honest note about those records: they also discuss a tab-style desktop treatment for the two audience categories, and that is not what shipped here. What shipped is a single carousel. We would rather flag the gap than let a working session stand in for a feature.

Related Builds

The Buy-vs-Build Question

A section pack costs less than a day of build and gives you a category row this afternoon. What it cannot give you is a card that reads your own collection records, because it was written for stores whose catalogue it has never seen — so every tile stays a manual copy of data you already maintain. Where that trade is worth making is the subject of Merchandising rules: buy or build?

Ready to Stop Maintaining Your Categories Twice?

If your homepage tiles and your collection records disagree, that is a data-modelling problem wearing a design problem's clothes. Contact us today to talk about what your homepage could read straight from your catalogue.

More builds