Pill Variant Picker With Merchandiser-Named Size Labels
Dawn prints an option value and nothing else. We extended its picker so a size pill carries two lines — a merchandiser's word for that size above the size itself — and falls back to positional defaults when nobody has written one.
Shopify option values are shared across variants, so "Travel size" cannot live on the option itself. We extended Dawn 15.4.1's variant picker to look up the first variant carrying each size value, read a label a merchandiser wrote on that variant, and print it above the value inside the pill — with positional defaults where the field is empty.
Fact Strip
- Client: LUS Brands — curly-hair care, loveurcurls.com
- Surface: Product detail page
- Templates served: Four —
product,product.new-design,product.kids-new-designandproduct.waiting-list - Complexity: Medium — roughly 350 lines across three snippets
- Attribution: Built on top of Dawn 15.4.1; this picker is our modification.
<variant-selects>, the option loop and theproduct-variant-optionsmarkup contract are Dawn's. The brand headings, thegroup--sizelayout hook and the metafield-driven size label are ours. - Status: Live — verified 2026-09-06
- Evidence: Strong — the theme's own snippets, plus a metafield export and client working-session records covering the same field
- Platform primitives: 3
The Problem
A variant picker on a Shopify product page shows option values, and an option value is a string the whole product shares. "8.5 fl. oz" belongs to the option, not to a variant, so the picker can tell a shopper which sizes exist and nothing about what any of them is for.
That gap gets expensive when one formula sells in three sizes. The shopper is not choosing between 8.5 and 3 ounces; they're choosing between the bottle that lives in the shower and the one that goes in a carry-on, and the numbers don't say which is which. The stock heading over the row had the same problem in a smaller way — Dawn labels the group with the option's own name, "Size", which was not the language this brand uses about its own range.
The Constraint
The fix has to come out of variant data, because there is nowhere else to put it. Option values are shared, so a per-variant label can't be attached to the value directly — it has to be looked up from a variant that happens to carry that value. Shopify gives Liquid no index for that lookup; you walk product.variants yourself.
Dawn renders options generically on purpose. Whatever the picker becomes, it still has to emit data-option-value-id, data-product-url and the visually hidden unavailable text, because <variant-selects> reads that markup to do variant resolution and URL rewriting. Break the contract and you break add-to-cart, not just the label.
One more limit shaped the scope: this picker serves four product templates, including a kids page with its own color treatment and a waiting-list page. Anything template-specific had to be a class or a setting, not a fork.
What We Built
Two snippets, sitting on top of Dawn's.
de_product_variant_picker.liquid wraps Dawn's <variant-selects> and decides how each option group presents itself. An option named formula renders under "Choose your formula". Any option whose name contains size renders with no visible legend at all and picks up a group--size class, because the labels inside the pills now carry the meaning the legend used to. Every other option keeps its own name as the legend, so a new option added in the admin still renders sensibly without a code change.
product-variant-options.liquid does the label lookup. For a size-type option, it scans product.variants for the first variant whose option at that position equals the value being rendered, reads variant.metafields.custom.option_custom_label from it, and renders that string as a small primary label above the option value inside the pill. The pill becomes two lines: the merchandiser's word on top, the literal size underneath. When the metafield is empty, positional defaults take over — the first size value renders as Standard, the second as Value, the third as Travel — so a product nobody has curated still reads as a considered range rather than as three volumes.
Everything else routes through Dawn unchanged. Pills, swatches and dropdowns all still go through the product-variant-options contract, including data-option-value-id, data-product-url and the visually hidden "sold out or unavailable" text on disabled values. Swatch-type options keep Dawn's swatch rendering and focal points untouched — the extension only claims the size case. Pill styling, including the selected state, the focus ring and the kids-page color override, is declared in the product section's own {% style %} block, which is what lets one picker serve four templates that don't look alike.
Dawn's original product-variant-picker.liquid is still in the theme and still in use, because featured-product.liquid and the legacy product section render it. The new picker is an addition beside it, not a replacement of it.
Why This Way
Putting the label on the variant makes it a merchandising field. Some ranges have a travel size and some don't; some have two sizes and a refill. A per-variant metafield lets that vary product by product without a developer, which is the same principle the rest of this product page runs on.
The positional fallback is there because the metafield will not always be filled, and a picker that renders a blank line above half its pills is worse than one that never had labels. Defaults keep the two-line shape consistent across a catalog that was filled in over time.
What it costs is Liquid work per render. The lookup walks the variant list for each option value until it finds a match, so a product pays a partial pass over its variants for every size pill it draws. On a three-size product that's nothing; the pattern doesn't generalize to a range with dozens of values. The styling decision has a cost too — pill rules live in the section's inline style block rather than in a component stylesheet, so they travel with the section instead of with the design system.
Why Not an App
Variant-picker and option-swatch apps are one of the standard rentals in this category, and they solve the presentation half of this problem well. The half they don't solve is where the label lives. An app that adds custom option labels keeps them in its own store, keyed to its own idea of a product, which puts a piece of merchandising copy outside Shopify and outside the export the brand already runs on its variants.
There's a rendering argument alongside it. Apps in this category generally hydrate the picker after the page paints, swapping the theme's markup for their own. That markup is what <variant-selects> uses to resolve a variant and rewrite the URL, so replacing it means the app now owns variant selection on the product page. Reading a metafield in Liquid keeps the correct picker in the first response and leaves Dawn's variant behavior exactly where it was.
Implementation Notes
- Option matching is by convention on the option name: a lowercase substring test for
size, an exact match forformula. Anything else falls through to its own name as the legend. - The label lookup breaks on the first matching variant, so it costs one partial pass over
product.variantsper option value rather than a full scan. custom.option_custom_labelis a variant-level metafield, so the same size value can carry different words on different products.- Positional defaults are Standard, Value and Travel, applied in option order when the metafield is blank.
group--sizeis a layout hook, not a style — it exists so the section's own CSS can lay out a two-line pill row differently from a one-line swatch row.- Pill styling, selected state, focus ring and the kids-page color override are declared in the product section's
{% style %}block rather than a component stylesheet. - Dawn's
product-variant-picker.liquidis retained and still rendered byfeatured-product.liquidand the legacy Dawn product section.
Edge Cases
- Unavailable values keep Dawn's
disabledclass and its visually hidden "sold out or unavailable" text, so the picker's accessible behavior is Dawn's, not a reimplementation of it. - Swatch and dropdown picker types pass through untouched, focal points included.
- Products with only a default variant render no picker at all.
- A fourth or later size value gets no custom label rather than an incorrect one — the defaults stop at three instead of cycling.
- An option the conventions don't recognize still renders, with its own name as the legend and no custom labels.
Platform Primitives Used
- Metafields —
custom.option_custom_labelon the variant is the whole label source; it is read at render time, never written to. - Liquid section schema and settings — the picker is rendered from the product section, which is where its styling and its per-template overrides are declared.
- Custom elements — the extension renders inside Dawn's
<variant-selects>element and preserves the attribute contract that element depends on.
Where It Runs
On the product detail page, mounted by four product templates: the standard product template, product.new-design, product.kids-new-design and product.waiting-list. One snippet pair serves all four, with the differences carried in section styling rather than in forked markup.
What This Demonstrates
Primary: variant swatches and shade pickers — the size-pill end of that capability, where the problem is naming rather than color.
It's also a metafield-driven PDP content build in miniature. The picker's copy is structured data read at render time, so renaming a size is an admin edit.
How We Know
Three snippets in the theme, two of them ours and one of them Dawn's original, alongside a metafield export that shows custom.option_custom_label as a variant-level definition on this store and client working-session records covering the same merchandising question. Confidence is strong: the code and the client record agree about what was being solved, and the metafield's existence in the export is independent confirmation that this was a deliberate data-model addition rather than a convenience in a template.
Related Builds
- Rebuilt 2026 PDP section with merchandiser-composable blocks and per-product ATC color — LUS Brands. The product section this picker renders inside, on the same four templates.
- Metafield-driven product information tabs with mobile accordion — LUS Brands. The same storefront answering the content half of the variant question.
- Color-accurate shade swatches driven by a merchandiser-editable hex registry — Nudestix. The same capability on a different storefront, where the option data missing from the platform is color rather than language.
The Buy-vs-Build Question
Custom option presentation sits right on the line where merchants usually reach for an app, and the decision turns on whether the data behind the label belongs in your variants or in a vendor's database. We set that argument out in full, with the platform limits that drive it, in variant limits and complex options: buy or build?.
Ready to Make Your Size Picker Say Something?
If your product page shows volumes where it should show reasons, the fix is usually a field and a lookup, not a subscription. Contact us today to talk about what your variant picker could be telling shoppers.