Kids Featured Collection: A Custom Element That Builds Its Swiper on Approach and Wraps the Theme's Own Product Cards
A <kids-featured-collection> element leaves its slides as static markup until an IntersectionObserver fires, initializes Swiper then, and tears everything down when the section is removed — around Dawn's card-product, not a card of its own.
A merchandised product carousel low on a long kids home page has no reason to build its slider before the shopper can see it. kids-featured-collection.liquid renders a custom element whose connectedCallback sets an IntersectionObserver with a 160px 0px 200px 0px root margin, initializes Swiper on first intersection and destroys it on disconnect. The slides are Dawn's own card-product, with card, quick-add and mask styles loaded only when the section's settings ask for them.
The Problem
Dawn's featured-collection section is a grid, styled by the theme's global settings, and it renders where it's placed at page load. The kids sub-brand on LUS Brands needed something the stock section can't be configured into: a carousel with the sub-brand's own type, accent color, arrow styling and slides-per-view — and real product cards inside it, with real prices, badges, secondary images and a working add to cart.
The section also sits well down a long editorial page, below several blocks of copy and imagery. So the brief had two halves that pull against each other: a rich, fully interactive product rail, and one whose slider is not constructed at the moment the document is parsed.
The Constraint
The cards had to be Dawn's card-product. LUS already carries its per-card behavior — badges, hover media, quick add, price formatting, sold-out state — in that one snippet, and a second kids card would drift from it the first time either changed. But card-product pulls its own dependencies with it: the card, price, quick-add and variant-picker stylesheets. Rendering it inside a carousel without emitting those sheets once per slide meant the section had to manage the snippet's assets, not just call it.
The pre-init markup had to hold its shape. Swiper's layout exists only after it initializes, so a section that waits to initialize has to be laid out as a carousel by the theme's own CSS before the library touches it. And the sub-brand styling had to be addressable per placement: the same section can be dropped on two templates with different accents, so the color and type choices couldn't live in a shared stylesheet.
What We Built
kids-featured-collection.liquid renders a <kids-featured-collection> custom element and hands it two kinds of configuration. Behavior goes in as data attributes — data-slide-gap, data-slide-gap-mobile, data-slides-mobile, data-slides-desktop. Appearance goes in as CSS custom properties emitted per section id: --highlight-color, --arrow-bg-color, --arrow-color, --slide-gap, a heading set (--kfc-h-size and --kfc-h-size-md, --kfc-h-lh and --kfc-h-lh-md, --kfc-h-weight, --kfc-h-align) and --kfc-header-swiper-gap. Two placements of the section get two scopes, and the stylesheet reads whichever scope it's inside.
Each slide is {% render 'card-product' %} with the section's image_shape, show_secondary_image, show_vendor, show_rating and quick_add settings passed through, so the kids rail inherits price formatting, sold-out handling, badges and quick add from the same snippet that renders the rest of the store. The first two cards render with lazy_load: false and the rest lazily, and skip_styles is set after the first card, so the card stylesheet is emitted once per section rather than once per slide.
kids-featured-collection.js defines the element. connectedCallback creates an IntersectionObserver with a 160px 0px 200px 0px root margin, disconnects it on the first intersection, and calls initSwiper. That initializes Swiper with the mobile slides-per-view at the base breakpoint, the same at 750px, and the desktop value at 990px, and passes both arrow pairs — a header pair and a footer pair, rendered twice in the Liquid — to navigation.prevEl and nextEl as arrays, so one instance drives two control clusters that show at different breakpoints. disconnectedCallback disconnects the observer and destroys the Swiper instance, so a theme-editor section reload leaves no observer behind.
Asset loading is decided by settings. Quick-add scripts and styles are emitted only when quick_add isn't none — the quick-add stylesheet plus the variant picker for standard, the four-script bulk set for bulk. mask-blobs.min.css loads only when image_shape is blob, and mask-arch.svg is inlined only for the arch shape.
Why This Way
The root margin is the point of the observer. 160px 0px 200px 0px extends the observed box past the section's own bounds, so the element is told it has intersected while the section is still short of the viewport rather than at the moment its top edge crosses. Until that fires, the section is markup and CSS.
Reusing card-product is the other half. The section could have carried a kids card of its own — it would have been simpler to style — but every card behavior LUS has built since would have had to be built twice. Passing the section's settings into the shared snippet means the kids rail can't drift from the collection grid, and it means the section has to own the snippet's dependencies, which is where the conditional asset loading comes from.
What we took on: 34 section settings, the widest surface in the kids range, which is a lot of theme-editor real estate for a merchandiser to read. A pre-init stylesheet that has to mirror Swiper's layout, so a Swiper upgrade that changes its DOM changes the pre-init rule too. And a carousel whose card is someone else's contract — a change to Dawn's card-product reaches this section whether or not anyone thought about it.
Implementation Notes
- 34 section settings: collection picker, max products, slides-per-view for mobile and desktop, slide gap for mobile and desktop, header-to-carousel gap, heading size, line height, weight and alignment per breakpoint, arrow and highlight colors, image shape, and the standard responsive padding quartet.
- The first two product cards render with
lazy_load: falseand the rest with lazy loading;skip_stylesis set to true after the first card so the card stylesheet ships once per section. - Arrow buttons are rendered twice — a header pair and a footer pair — and both are passed to Swiper's
navigation.prevElandnextElas arrays; one instance, two control clusters, shown at different breakpoints. - Quick-add assets follow the
quick_addsetting: nothing fornone, the quick-add stylesheet plus the variant picker forstandard, the four-script bulk set forbulk. mask-blobs.min.cssis loaded only whenimage_shape == 'blob';mask-arch.svgis inlined only for the arch shape, so a rectangular-card placement emits neither.- Appearance travels as CSS custom properties scoped to the section id, so two placements on one page can carry different accents from one stylesheet.
- The Swiper configuration is three breakpoints — base, 750px, 990px — with slides-per-view read from the data attributes rather than hard-coded in the script.
Edge Cases
disconnectedCallbackdisconnects a still-live observer inside atry/catchand destroys the Swiper instance, so theme-editor section reloads don't leak observers.initSwiperdestroys any existing instance before creating a new one, which makes re-initialization onshopify:section:loadsafe.- No collection selected renders the header and nothing else — an authored empty state rather than an empty slider shell with live arrows.
- Data attributes are parsed defensively —
parseInt(...) || 20, anisNaNcheck on the mobile gap that falls back to the desktop gap,parseFloat(...) || 2— so a cleared setting can't produce aNaNslides-per-view. - The previous-arrow buttons ship with
swiper-button-disabledalready applied in the Liquid, so they're visually disabled before Swiper takes over and never flash as enabled at slide one. - Both arrow clusters are
aria-hidden, with translatedgeneral.slider.previous_slideandnext_slidelabels on the buttons, so the decorative duplication stays out of the accessibility tree.
Platform Primitives Used
- Custom elements —
<kids-featured-collection>owns its own lifecycle: observe on connect, construct on approach, destroy on disconnect. - Intersection Observer API — the
160px 0px 200px 0pxroot margin is what "on approach" means here; unobserved after the first hit. - CSS custom properties — the sub-brand's colors, type and gaps, scoped per section id so placements don't share a look.
- Liquid section schema and settings — 34 settings, several of which decide which assets the section emits at all.
Integrations in Play
- Swiper — the slider library. The element constructs one Swiper for its own
.kfc__gridon first intersection and destroys it on disconnect.
Where It Runs
Two page templates mount it: the kids home page, where it's the merchandised rail on the sub-brand's landing, and the redesigned about page. The section is indexed under home and collection listing as a product rail, and because its cards are Dawn's card-product, a card change made for the collection grid shows up here on the next render.
What This Demonstrates
- Collection merchandising: rails, tiles and in-grid promotion — the primary capability: a merchandiser-configured product rail whose card renderer is the one the rest of the store uses.
- Quick view and in-grid add to cart — the
quick_addsetting decides whether the rail's cards can add to cart in place, and which scripts the section emits to make that work. - Media and asset loading strategy — construction deferred to an observer callback, and stylesheets and scripts emitted only when a setting asks for them.
How We Know
Seven theme files, roughly 700 lines across them: the section, the element's script in source and minified form, its stylesheet in both forms, and the two JSON templates that place it. Documented from the code; no client-side record of the decision survives. The kids sub-brand's wider redesign is documented in client records, but nothing in them speaks to this carousel specifically, so the reasoning above is reconstructed from what the section and the element do.
Related Builds
- Adults/Kids audience-switching header with automatic menu swap — the same sub-brand, one level up: how the storefront decides a visitor is on the kids side at all.
- Kids FAQ page: single-level variant of the FAQ engine — another section in the kids range on the same storefront, where a shared engine is re-skinned for the sub-brand rather than rewritten.
- Merchandisable homepage product carousel with a tag-driven badge engine and inline add-to-bag — the adult homepage's rail on the same storefront, which made the opposite card decision: a bespoke snippet rather than Dawn's.
- Featured-collection product card with metafield hero/hover media and a tag-driven badge stack — the card that rail uses, for the comparison with
card-product.
The Buy-vs-Build Question
A carousel app buys a rail configured in a dashboard and rendered by the app's own card, which means a second card to keep in step with the theme's. Building it bought one card renderer for the whole store and cost a wide settings surface and a slider that's ours to maintain. Where that trade lands for a given catalog is the subject of merchandising rules: buy or build?.
Provenance & Evidence
- Client: LUS Brands — loveurcurls.com
- Surfaces: Home page, collection listing
- Templates served: two — the kids home page and the redesigned about page
- Complexity: Medium
- Scale: roughly 700 lines across seven files
- Attribution: Deploi-authored. The section, the custom element, its script and its stylesheet are ours. They run inside a theme built on Dawn 15.4.1 and deliberately render Dawn's own
card-productunchanged. - Status: Live, verified 2026-09-07
- Evidence: seven theme files read from the theme
- Confidence: Moderate — documented from the code; no client-side record of the decision survives
- Primary capability: Collection merchandising: rails, tiles and in-grid promotion
Ready for a Sub-Brand Rail That Uses the Cards You Already Have?
If a second look for a second audience has left you with a second product card, you're paying for every card change twice. Contact us today and we'll show you how a sub-brand section can borrow the store's card and still look like its own thing.