Merchandisable homepage product carousel with a tag-driven badge engine and inline add-to-bag
A two-column row — optional linked lifestyle image, Swiper rail — where every label, color, badge and product is a theme-editor setting, and each card can add to bag without leaving the page.
Dawn's stock featured-collection section renders a plain grid from one collection, with no badge vocabulary and no lifestyle slot. We built a replacement with 42 theme-editor settings and a repeatable product block: a linked lifestyle tile beside a Swiper rail, badges computed in Liquid from product tags and compare-at price, metafield-driven card and hover imagery, and add-to-bag posting through the theme's own AJAX cart.
Fact Strip
- Client: LUS Brands — haircare DTC, Shopify
- Surfaces: homepage, collection listing
- Templates served: one — the
indextemplate - Complexity: High
- Attribution: Deploi-authored. The section, its card snippet and its script are ours; they sit in a theme built on Dawn 15.4.1, and the add-to-bag deliberately posts through Dawn's own
<product-form>and AJAX cart rather than a cart of ours. - Status: Live, verified 2026-09-06
- Scale: roughly a thousand lines across the section, the card snippet and the script
- Evidence: Strong — read from the theme, corroborated by client working-session records and a metafield export
The Problem
The homepage needed one merchandising module that could do three things at once: sell a hand-picked mix of hero SKUs — bundles, kits, new launches — beside a lifestyle image, carry promotional badges the marketing team controls without a developer, and let a shopper add to cart without leaving the page. Dawn's stock featured-collection section does none of them. It renders a plain grid, sourced from exactly one collection, with no badge vocabulary and no slot for an editorial image. A homepage row is the highest-traffic merchandising surface a DTC storefront has, and on a stock theme it is also the least configurable one.
The Constraint
Theme-editor authorability was the hard requirement, and it shaped everything downstream: every label, color, badge and product had to be editable by a merchandiser rather than hard-coded, which meant the section's settings schema had to carry the whole vocabulary up front. Liquid cannot compute badge state on the client, so all badge logic had to resolve at render time from product tags, compare-at price and block settings — no post-paint decisions. And the section does not bundle Swiper: it expects the library to be on the page already, enqueued somewhere else in the theme. So its script cannot assume the library exists when it parses. It has to work whether it loads before or after the thing it depends on.
What We Built
de_featured_collection.liquid renders a two-column row — an optional linked lifestyle image and a Swiper rail — configured by 42 schema settings and a repeatable product block. The product source is a switch: a collection picker, or a manual product list assembled from blocks. The manual mode is the one that matters — it lets a merchandiser hand-pick the mix instead of curating a collection nobody will ever browse.
Each slide renders de_fc_product_card.liquid, and the card is where the density is. It resolves its primary image from the custom.feature_img product metafield and falls back to featured_media. It resolves hover media from custom.feature_hover_img — image or video — and falls back to product.media[1]. It computes badges from block toggles combined with product tags (NEW, VALUE, FREE-SHIPPING-ELIGIBLE, bestseller variants) and adds a computed Save % or Save $ badge derived from compare_at_price, switching between the dollar amount and the percentage depending on whether the product carries the VALUE tag. It renders a twelve-word truncated description with a per-block override, a Yotpo review widget mount, and an inline {% form 'product' %} wrapped in Dawn's <product-form> so the add-to-bag posts through the theme's existing AJAX cart rather than a second cart implementation.
de_featured_collection.js handles the load-order problem directly: it polls for the global Swiper on a 40ms interval up to 80 times, then initializes with slidesPerView: 'auto' and breakpoint-specific gutters, disables the arrows when there is only one slide, lazy-loads hover-video sources on mouseenter, and binds shopify:section:load and shopify:section:unload so the carousel re-initializes and tears down cleanly per section instance.
Section CSS custom properties — accent, teal, star, sale-badge, radii — are written inline from the color settings, so the same section can be re-themed per placement without a new section file.
Why This Way
The card is a snippet of its own rather than a copy of Dawn's card-product, and that was a deliberate fork. The badge vocabulary, the metafield-driven imagery and the review hook are specific to this module; pushing them into the shared card would have leaked homepage logic into the card used across the collection page and search.
Polling for Swiper rather than importing it keeps the section independent of load order for a library the theme enqueues globally — the alternative was bundling a second copy. Binding the section lifecycle events keeps the carousel alive through theme-editor re-renders, which is where hand-rolled sliders usually break and where a merchandiser forms their opinion of the module.
Three costs came with that. There are now two card renderers in the theme, and a change to card behavior has to be considered in both. The Swiper poll is a wait window, not a guarantee. And the badge vocabulary is a set of exact tag spellings, so the tags on a product and the logic in the snippet are a contract maintained by convention rather than by the schema.
Why Not an App
This displaces the usual pairing of a product-slider app and a badge or labels app. Both classes work the same way: they ship their own JavaScript, read the grid after the page has painted, and re-render it with their markup. Two consequences follow. Badges arrive after the first paint, so the highest-traffic row on the site renders once without them and once with them. And a slider app that offers add-to-cart brings a second cart script alongside Dawn's. That's how a storefront ends up with two systems that both think they own your cart drawer.
Doing it in Liquid keeps badges in the server-rendered HTML — the version a crawler and an answer engine actually read. It also keeps add-to-bag inside the theme's own <product-form> and cart events, so every other cart-aware component on the site hears the add without being told about this section.
Implementation Notes
- Product source is a select —
collectionormanual— andslide_countis computed up front, so an empty selection renders an authored empty-state message rather than an empty rail with live arrows. - The first two cards render with
loading="eager"and the rest lazily. That's an explicit LCP concession for a row that sits above the fold on the homepage, made deliberately rather than inherited from a default. - The card
srcsetis hand-built with width guards — a candidate is only emitted ifcard_primary_image.widthis at least that width — and forcesformat: 'webp', so the browser is never offered an upscaled candidate. - The
sizesattribute is computed fromsettings.page_width, so the responsive hints follow the theme's own container setting instead of a breakpoint hard-coded in the snippet. - Hover video sources are held in
data-srcand only promoted tosrcon firstmouseenter, so a row of video-hover cards fetches no video bytes on page load. de_featured_collection.min.jsis an esbuild output of the.jssource, produced by the theme's asset build script rather than separately authored, so the two files cannot drift by hand.- The section is live on the
indextemplate configured with a manual product source and hand-picked blocks — the three-step system, the ultra-moisturizing kit, curl-defining products — each carrying its own bestseller or new badge setting. - CSS custom properties for accent, teal, star, sale-badge and corner radii are written inline from settings, so a second placement of the same section can be re-themed without touching the stylesheet.
Edge Cases
- Sold-out products render a "Sold out" badge and swap the add-to-bag form for a link to the product page, so the card never presents a button that cannot succeed.
- Single-slide carousels disable both arrows rather than leaving dead controls a shopper can click to no effect, which is the state a manually curated rail lands in most often.
- An empty selection — a deleted product, an emptied collection — renders the authored empty-state message rather than a blank rail with live controls above a fold-height gap.
- Products with no media at all fall back to
placeholder_svg_tag, so a catalog gap shows as a placeholder rather than a broken image box on the homepage. - The hover swap is skipped entirely when a product has neither a second image nor a hover metafield, so nothing flickers on hover.
- A rejected
video.play()is swallowed — autoplay policies vary — andcurrentTimeresets onmouseleave, so a half-played hover video restarts rather than resuming mid-frame. - Section unload clears the Swiper-wait timeout and the resize listener before destroying the Swiper instance, so a theme-editor re-render leaves nothing behind.
Platform Primitives Used
liquid-schema-settings— 42 settings plus a repeatable product block carry the entire merchandising vocabulary into the theme editor.metafields—custom.feature_imgandcustom.feature_hover_imgdecide the card and hover imagery per product, with media fallbacks behind them.cart-ajax-api— inline add-to-bag posts through Dawn's<product-form>and the theme's existing AJAX cart.theme-blocks— each hand-picked product is a block. That is what makes the manual source orderable and configurable per product.
Integrations in Play
- Swiper — the rail wraps the theme's globally enqueued Swiper instance; the section polls for it rather than bundling its own copy.
- Yotpo — the card places the vendor's review widget mount rather than standing up a second review surface. We have not verified against the live page what that mount renders inside the rail, so we are not claiming a working ratings row on these cards.
Where It Runs
On the homepage, mounted by the index template, with the card snippet also serving rail placements on collection listings. One template, one section, one card renderer — configured in the live theme with a manual product list rather than a collection, which is the configuration that made the manual source worth building in the first place.
What This Demonstrates
Metafield-driven PDP content blocks — the card's imagery and its editorial copy come from structured product fields, so swapping a homepage hero image is a merchandising edit rather than a deploy.
It also sits in the storefront's collection-merchandising work — rails, tiles and in-grid promotion — and in its in-grid add-to-cart pattern.
How We Know
Four theme files, read straight off the live storefront: the section, the card snippet, and both the source and the built script. Alongside them sit the client's own product metafield export — it names the same two imagery fields the card snippet reads — and working-session records covering the standardized cross-sell card this carousel renders. Confidence is strong. If you'd rather check that than take it, the export and the snippet are the two documents to put side by side.
Related Builds
- Main collection grid: merchandisable promo tiles, load-more and filter blocklist — Nudestix. The same merchandiser-configurable instinct applied inside a paginated grid.
- Product card with award badges, per-variant content payload and per-card structured data — Nudestix. A card renderer carrying its own badge vocabulary.
The Buy-vs-Build Question
A slider app and a badge app together are cheap monthly and expensive architecturally — two scripts, one grid, and badges that arrive after paint. Where that trade is worth making, and where the structured-fields route pays for itself instead, is the subject of metafields and metaobjects architecture: buy or build?.
Ready to Make Your Homepage a Merchandising Surface?
If your best row is the one your marketing team cannot edit, that is a build problem, not a content problem. Contact us today to talk about what a merchandiser-owned homepage module would look like on your theme.