Shopify Builds>Nudestix>Color-accurate shade swatches driven by a merchandiser-editable hex registry

Color-accurate shade swatches driven by a merchandiser-editable hex registry

Roughly 250 Shade Name:#hex pairs live in one theme setting. The product section resolves every option value against them in Liquid, groups the results by finish, and paints each swatch server-side.

Color cosmetics cannot be sold from a dropdown, and this theme's variant picker rendered text buttons only. We put roughly 250 shade-to-hex pairs into a single theme setting — with escape hatches for duo-tone shades and for a shade name that means different pigment on different products — and resolved them in Liquid, so the swatch is painted in the first frame with no color flash and no swatch app.

Fact Strip

  • Client: Nudestix — color cosmetics DTC, Shopify
  • Surfaces: product detail page, collection listing
  • Templates served: three — product, product.bundle, product.quickview
  • Complexity: High
  • Attribution: Deploi-authored. The registry format, the Liquid resolver, the finish grouping, the normalization snippet and the badge logic are ours, written inside a theme based on Dawn 6.0.2, which we did not author.
  • Status: Live, verified 2026-09-06
  • Scale: roughly 700 lines across the product sections and the shared snippet
  • Evidence: Strong — read from the theme, with client specification and task-register records on the same work

The Problem

You can't sell a lipstick from a dropdown. The shopper has to see the shade, and seeing it is the decision. This catalog makes that harder than the usual swatch problem in four specific ways. There are hundreds of shade names across the range. The same shade name is reused across products with genuinely different pigment — a shade in the matte lux line isn't the same color as the refill that shares its name. Some products are duo-tone and need two colors inside one swatch. And shades belong to finish families — Matte, Matte Lux, Bloom — which a shopper compares within, not across, so a single flat row of forty circles is the wrong presentation even when every color in it is correct.

The Constraint

Dawn 6.0.2 predates Shopify's native swatch metaobjects, and the theme's variant picker renders text buttons and nothing else, so there was no swatch primitive to configure. Shade hex values are a merchandising asset that changes with every launch, which rules out hard-coding them in CSS or shipping a deploy for a color correction. Shopify option values are plain strings shared across products, so a bare name-to-hex map is ambiguous by construction — the map cannot tell you which product's version of a shade name it is looking at. And the option values themselves carry punctuation: apostrophes, exclamation marks, ampersands in real shade names. Those characters have to round-trip safely into CSS attribute selectors, into data- attributes and back out again, on three templates.

What We Built

The registry is a single theme setting, settings.swatch_colors — a textarea in settings_schema.json holding roughly 250 comma-separated Shade Name:#hex entries. Two extensions to that format do the disambiguation work. #hex|Product Title scopes a hex to one product; it is matched case-insensitively against product.title with a contains test run in both directions, so a partial title on either side still resolves. #hexA&#hexB renders a duo-tone swatch as a hard-edged linear-gradient(90deg, A 0 49%, B 50%) rather than a blend, because a cosmetic duo is two colors, not a gradient between them.

main-product.liquid splits that setting at render time and resolves each option value against it. Shade grouping runs off variant.metafields.my_fields.stick-type: the section walks the variant list, collects the distinct finish types, and renders either a single flat swatch row — for one-finish products and for quick-view — or one labeled fieldset per finish, "Matte Shades:", "Bloom Shades:".

Every swatch input carries the identity the rest of the storefront needs: data-sku, data-id, data-face from theme.variant_face_data, data-variant_text from my_fields.color_text, a variant-fil attribute holding the finish, and a symbol-stripped data-value produced by product-get-options-strip-symbols.liquid — a shared snippet that removes 32 punctuation characters and downcases, so the same key works from CSS, from JavaScript and from the collection page's swatch overrides.

Merchandising badges are per-variant metafields. custom.new_badge renders a NEW! pill, custom.restocked renders RESTOCKED, and custom.is_special_variant drives special styling. Adjacent-NEW detection inspects the previous and next entries in the uniq'd variant list and switches to reduced badge padding, so two consecutive badges do not collide in the row.

The selected shade name and its marketing color text are echoed into the label — "Shade: Nude 4.5 - Warm Beige" — and a Shade Finder button appears beside it when the product carries the quiz snippet metafield. Quick-view rendering caps the visible swatches at seven and appends a "+N MORE" link to the full product page, and custom.display_shades_carousel combined with a variant count of five or more switches on a separate shades slider below the gallery.

Why This Way

One theme setting gives a single editable source of truth for a value that, on this theme version, is neither a product property nor a variant property in Shopify's data model. Shade color becomes a merchandiser action rather than a developer ticket — a color correction ships the day someone notices it, not with your next release. Resolving hex in Liquid rather than JavaScript paints the swatch in the first frame, with no flash of gray circles filling in after hydration on a page whose whole job is showing color. And the symbol-stripping snippet exists because the same shade key has to survive a CSS attribute selector, so normalization is centralized in six lines instead of reinvented at every call site.

A textarea is a text field, not a schema, and that is what it costs. Nothing validates a hex, catches the same shade name entered twice with two different colors, flags a stray leading space or a zero-width character pasted from a spreadsheet, or tells a merchandiser that renaming a shade in the admin has just detached it from its color. Those failures are invisible in the admin and silent on the page. The registry is also re-split inside the option loop rather than parsed once, so a long shade range pays that string work more than once per render. We chose editability over enforcement and made the failure quiet on purpose: an unmatched shade renders unstyled and the row keeps its shape. If you build this, budget for the day the registry becomes a metaobject.

Why Not an App

Swatch apps are one of the most commonly rented categories on Shopify, and the shape of what they sell is consistent: the color map lives in the app's own database, the swatches are injected into the page after it paints, and the store pays monthly for both. Each of those three is a problem here.

Color data in a vendor database is brand data held outside Shopify, which matters when the same hex values feed the collection page and the shade finder. Injection after paint is the wrong order of operations on a page where color is the product: your shopper sees the fallback first. And a script on every product page is a script on every product page. Rendering the swatch server-side inside the native variant radio group means it participates in the normal form submit and the theme's own variant-change event, with no third party in the path between choosing a shade and adding it to the cart.

Implementation Notes

  • settings.swatch_colors is a single textarea in a "Product Variant Color" group in settings_schema.json, holding roughly 250 Name:#hex pairs; settings_data.json carries the live value, so a color change is a theme-settings edit.
  • Two escape hatches sit inside the entry format: Name:#hex|Product Title scopes a hex to one product, and Name:#hexA&#hexB produces linear-gradient(90deg, A 0 49%, B 50%) for duo-tone shades — a hard edge, not a blend.
  • A near-white swatch, #FAF7F2, is given an explicit 1px gray border so it stays visible against a white product page instead of disappearing into it.
  • Shade families group on variant.metafields.my_fields.stick-type. The section builds the distinct type list in Liquid first, then emits one labeled fieldset per finish rather than one flat row.
  • product-get-options-strip-symbols.liquid normalizes an option value by removing 32 punctuation characters and downcasing. It is used for data-value and for the collection page's swatch override selectors, so both sides of that pairing agree by construction.
  • Per-swatch attributes are data-sku, data-id, data-face, data-variant_text, variant-fil, attr_color and data-alt-variant-name — enough for the shade carousel, the quick view and the collection page to address the same swatch without re-deriving it.
  • custom.new_badge, custom.restocked and custom.is_special_variant drive the badging, and hasAdjacentNewBadge inspects variants[index-1] and variants[index+1] to switch to reduced padding when badges would otherwise collide.
  • Quick-view rendering caps visible swatches at seven and appends a "+N MORE" link to the product page, so a forty-shade product does not turn a modal into a scroll.

Edge Cases

  • Products with only a default variant skip the picker entirely, behind a has_only_default_variant guard, so a single-SKU product does not render a one-swatch row.
  • Shade names containing apostrophes and other punctuation go through the symbol-stripping snippet rather than ad-hoc escaping at the point of use.
  • The option label switches from "Shade:" to "Color:" for the magnetic eye color family, because that range is not merchandised as shades.
  • A shade with no registry entry renders as an unstyled swatch — colorHex stays null — rather than breaking the row or emitting invalid inline CSS.
  • The variant list is uniq'd on option1 before badge adjacency is computed, so multi-option products do not mis-index and badge the wrong neighbour.
  • Quick-view context renders a compact row, so the modal does not scroll on mobile even on the longest shade ranges.

Platform Primitives Used

  • metafields — finish type, marketing color text, badge flags and the shade-carousel switch all arrive as product and variant metafields.
  • liquid-schema-settings — the whole hex registry is one theme setting, editable without a deploy.
  • custom-elements — the swatches sit inside the theme's native variant radio group, so the existing variant-change behavior picks them up unchanged.

Where It Runs

On the product detail page and, through the shared normalization snippet and the swatch override selectors, on the collection listing. Three templates mount it: the standard product template, the product.bundle template where kit contents are shade-selectable, and product.quickview, which renders the compact seven-swatch variant of the same row.

What This Demonstrates

Metafield-driven PDP content blocks — shade presentation is assembled at render time from structured fields plus one theme setting, so a launch is a merchandising task rather than a deployment.

It is also this corpus's clearest instance of natively built variant swatches and shade pickers: swatches, finish grouping and badging, conventionally three app subscriptions, running from metafields and one setting.

How We Know

Five files read directly from the live theme — two product sections, the shared normalization snippet and both theme-settings files — alongside a client specification record and a task-register entry covering the shade-presentation work on the same page. The specification is granular enough to name swatch spacing, which is a useful signal about how closely this surface was reviewed. Confidence here is strong: the code and the client record agree about what was being built, and the registry's live value is readable in the theme's own settings.

Related Builds

The Buy-vs-Build Question

Swatches are the classic rent-versus-build decision on Shopify, and the honest answer depends almost entirely on whether the color data belongs in the brand's own data model or in a vendor's. That argument, and where the maintenance actually lands, is set out in metafields and metaobjects architecture: buy or build?.

Ready to Get Your Shades Right?

If your catalog is color and your product page is text buttons, the gap between those two things is costing you the sale. Contact us today to talk about what a native swatch system on your theme would take.

More builds