Metafield-Driven Product Information Tabs With Mobile Accordion and Variant Refresh
How To, Ingredients, FAQs and Tips render below the buy box from metafields — as tabs on desktop, as a native accordion on mobile, and re-resolved from the server every time a shopper picks a different variant.
Liquid renders once per request, so a tab that should disappear when a variant has no ingredients cannot be decided at page load alone. We built a tabs section that computes tab presence server-side from metafield emptiness, renders the same content twice for desktop tabs and a mobile accordion, then re-fetches itself on variant change and swaps four regions in place.
Fact Strip
- Client: LUS Brands
- Surface: Product detail page
- Templates served: 4
- Theme settings exposed: 48
- Complexity: High
- Attribution: Deploi-authored. The section and its snippets are ours; the variant-change event it subscribes to is Dawn 15.4.1's own publish/subscribe channel.
- Status: Live, verified 2026-09-06
- Evidence: Strong — code plus client working-session records
The Problem
Below the buy box, the rebrand needed four separate bodies of educational content: application steps, ingredients, FAQs and styling tips. Not every product has all four. Some products vary by variant — the ingredients for one formula are not the ingredients for another — and a curly-hair catalog is exactly the kind of catalog where that matters.
Two behaviors had to hold at once. A product with no tips can't show a Tips tab with an empty panel behind it. A shopper who switches from one formula to another has to see the ingredients for the formula they just picked, not the one they arrived on. Shopify gives you no mechanism for either. A tab is a block a merchandiser adds, and the platform won't withdraw it because a field came back blank.
The Constraint
Liquid renders once per request. Tab presence therefore has to be decided server-side, from whether each metafield resolves to something — while variant switching happens on the client, after that decision was already made and printed into the HTML.
The theme editor compounds it. There is no way to conditionally register a block, so the section cannot ask Shopify to hide a tab; it has to compute presence itself and then suppress the chrome after render. And presence is not a single-variant question: on a product where only some variants carry ingredient data, the tab still has to exist for the product so the shopper who lands on a bare variant can switch to a populated one and find it there.
What We Built
de_product_info_tabs.liquid — 48 theme settings — resolves four content sources with a variant-then-product fallback: How To from a configurable namespace and key, defaulting to custom.how_to_use; Ingredients from ingredient_tab.title, custom.key_ingredients and custom.ingredients_list_content; FAQs from custom.faqs; Tips and Tricks from custom.tips and custom.tricks.
It hands those to product-info-tabs.liquid, a snippet of roughly 3,900 lines that renders each body twice: once into a desktop tablist with roving hidden state, and once into a <details>-based mobile accordion. Every child snippet takes an id_suffix of -m for the mobile pass, so aria-controls and aria-labeledby pairs stay valid in both trees instead of colliding on duplicate ids.
Variant refresh is the interesting half. The section subscribes to Dawn's variantChange event, fetches the product URL with the new variant and its own section_id, parses the response with DOMParser, and swaps four regions in place: the How To content, the key-ingredients region, the ingredients-list modal panels, and a hidden state marker that carries the recomputed data-has-ingredients flag. That marker is the mechanism that makes the disappearing tab work — presence is recomputed by the same Liquid that rendered the page, and rides back in the same response. If the Ingredients tab was active and has just become empty, the section clicks the first remaining tab rather than leaving the shopper on a blank panel. It then dispatches its own update events so sliders inside the swapped regions re-initialize.
The FAQ panel renders as an accordion with the first item open, beside an optional merchandiser-configured banner — image, eyebrow, heading, CTA and caption — used on the adult templates to route shoppers into the curl quiz. Tips and Tricks render as two icon-card grids with configurable group titles. Those titles got their own metafields on purpose, out of a client working session: the group headings were content the brand would want to change later, and content that changes belongs in a field, not in a theme setting nobody remembers exists.
Why This Way
Re-fetching the section by id reuses the exact Liquid that rendered the page. Variant-specific content is never duplicated in JavaScript, the fallback chain lives in one place, and the recomputed presence flag rides back in the same response instead of needing a second availability endpoint. Any other approach means writing the resolution logic twice and keeping both copies in agreement forever — a promise nobody keeps.
Desktop and mobile are rendered separately rather than restyled from one DOM, because the two layouts differ structurally — a tablist is not a set of <details> elements — and the mobile accordion carries per-design classes the desktop layout has no use for. We took two costs for that, deliberately. The same content bodies exist twice in the markup, desktop and mobile. And every variant change costs a full section render of its own, on top of the one the theme is already doing for the buy box — two section renders per click, not one, where a payload already in the page would have cost zero. Pre-serializing every variant's content would have avoided the round trip and reintroduced the duplication problem in a worse place.
One more thing you should hear from us. The metaobject reads are defensive — long fallback chains across field names, so old-shaped and new-shaped records both render. That keeps a half-renamed field working, and it means a genuine schema change fails quietly rather than loudly.
Why Not an App
Product-tab and FAQ apps are the ordinary purchase here, and they work. What decided it was where your content ends up living: those apps keep the copy in the vendor's own database. Hold it in Shopify metafields and metaobjects instead and it's editable in the admin next to the product it belongs to, it moves with the store if you ever change themes, and it renders server-side — the ingredient list and the FAQ answers are in the initial HTML, so crawlers and answer engines get them without waiting for a script.
There's a second reason, and it is specific to this section. A tabs app decides for itself whether a tab appears. Here that decision is Liquid's, recomputed per variant and returned in the same response as the content, so tab presence and tab content can never disagree. Buying the tabs means the decision lives wherever the app resolves the content, which is not your theme.
Implementation Notes
- The section is restricted to product templates and ships with a preset, so a merchandiser can add it in the theme editor but cannot mount it on a page where its metafield sources do not exist.
- Tab presence is computed per request, and a hide-when-empty setting additionally scans every variant's ingredient fields, so the tab survives for products where only some variants carry ingredient data.
- The active panel falls through How To, then Ingredients, then FAQs, then Tips; when only one panel survives, the tab chrome is suppressed entirely and the content renders on its own.
- FAQ and tip element ids are suffixed for the mobile copy, keeping every
aria-controlsandaria-labeledbypair pointing at a real element in both trees. - Answers render through the
metafield_tagfilter and accept both the current and the legacy field name on the FAQ record, so older entries keep rendering while new ones use the current shape. - A
custom.how_to_designvalue, variant first then product, can override the section's How To layout setting, mapping loosely typed strings onto the four available layouts.
Edge Cases
- The whole section renders nothing at all when all four content sources come back empty, rather than leaving an empty band of tab chrome below the buy box.
- The Ingredients tab, its desktop panel and its mobile accordion item are hidden together, and the accordion item is force-closed, when a variant has no ingredient data.
- Fetch failures are swallowed except for deliberate aborts, so a dropped request leaves the previous variant's content on screen instead of blanking the panel.
- If the theme's publish/subscribe channel is not yet defined when the section initializes, the binding retries once on
DOMContentLoaded. - A guard attribute prevents double-binding when the theme editor re-renders the section, which is otherwise how you end up with two fetches per click.
Platform Primitives Used
- Metafields — the four content sources, each resolved variant-first then product.
- Metaobjects — FAQ entries and the structured content records the tabs render.
- Section Rendering API — re-renders this section by id on variant change, returning both content and recomputed tab presence.
- Liquid section schema and settings — 48 settings covering layout choice, banner content, group titles and the configurable metafield namespace.
Where It Runs
The product detail page, on all four product templates — standard, new-design, kids and waiting-list — directly below the buy box built in the same rebrand. It sits in the same section group as the product section, so the variant event it listens to is the one the buy box has just acted on.
What This Demonstrates
- Variant-aware content and media swapping — content and tab availability both re-resolve per variant, server-side.
- Metafield-driven PDP content blocks — every word in these tabs is a structured field a merchandiser owns.
How We Know
Four files read directly from the live theme — the section and the three snippets it renders — plus twelve client-side records: working-session transcripts and product metafield exports. The exports are what raise this from moderate to strong. The tips and tricks fields appear in the client's own exported list as new fields, and as tab content in the Liquid; the FAQ field appears as carried-over content, which matches the way the code treats it. Metaobject definition handles are held back here: they are inferred from the code rather than confirmed in the admin, and an inferred handle is not a fact.
Related Builds
- Rebuilt 2026 PDP section with merchandiser-composable blocks — the buy box directly above these tabs, on the same four templates.
- Metafield-driven PDP content system — the same content problem on Nudestix, where legacy copy had to be parsed out of the product description instead.
- Help center FAQ engine — the same FAQ content model at storefront scale rather than product scale.
The Buy-vs-Build Question
A tabs app installs in an afternoon and is the right call for a storefront whose product copy is the same for every variant. This catalog is the opposite case: formula-level ingredients, four templates, and content the brand wanted to own in its own admin. Our reasoning on where product content should live is set out in the metafields and metaobjects architecture decision.
Ready to Get Your Product Content Out of an App?
We move product copy into Shopify's own data model and build the theme that renders it. Contact us today to talk about what your product pages should be able to say.