STK
Setup Guide · Shopify

Show shoppers their local size labels

Let a US visitor see US 8 where an Australian visitor sees AU 12 — automatically, based on the shopper’s country. Your Shopify variants, orders, inventory and checkout are never changed; only the displayed size label is.

Stk stores one canonical size per variant (e.g. AU 12) and, for each Shopify variant, writes the per-region labels as metafields in the stk namespace (for example stk.labels = {"AU":"AU 12","US":"US 8","default":"AU 12 US 8"}). A tiny theme snippet prints those labels and the shopper’s country into the page, and a tiny script swaps the visible size label on the product page and in the cart. The real Shopify option value (e.g. AU 12 US 8) is left alone, so checkout stays correct.

Back up your theme before you start

You’re about to edit theme code — make a safety copy first so you can roll back in one click:

  • In Online Store → Themes, find your live theme and click its … (three dots) button, then Duplicate.
  • Downloading the theme (same … menu → Download theme file) is also a safe strategy and keeps an off-Shopify copy.

When your backup is done, open the same … menu → Edit code to begin.

You touch four files. Stk-provided Two of them are supplied by Stk and are exactly the same for every store — create them and paste the contents verbatim. Your theme The other two are your theme’s own files, where you add just three short lines.

1. snippets/stk-variant-labels.liquid  Stk-provided · paste as-is

Create this new file in your theme’s snippets folder and paste:

snippets/stk-variant-labels.liquid
{%- comment -%}
  Stk Variant Display Bridge — snippet (luca-1 dev store first; NOT the Togs live theme).

  Emits a JSON map of { shopifyVariantId: stk.labels } plus the shopper's country, so client JS can
  show a region-specific size label WITHOUT changing the native Shopify option value (which stays
  for checkout, e.g. "AU 12 US 8").

  INSTALL:
  - Render once in layout/theme.liquid for cart item data.
  - Render once inside sections/main-product.liquid for the current PDP's product variants.

  Reads the metafields written by tools/variant_metafield_writer.php (namespace "stk").
{%- endcomment -%}

<script id="stk-variant-labels-data{% if section != blank %}-{{ section.id }}{% else %}-global{% endif %}" data-stk-variant-labels-data type="application/json">
{
  "country": {{ localization.country.iso_code | default: 'AU' | json }},
  "variants": {
    {%- assign stk_emitted_label = false -%}
    {%- assign stk_product = product | default: product_resource -%}
    {%- if stk_product != blank and stk_product.variants != blank -%}
      {%- for variant in stk_product.variants -%}
        {%- assign labels = variant.metafields.stk.labels -%}
        {%- if labels != blank -%}
          {%- if stk_emitted_label -%},{%- endif -%}
          "{{ variant.id }}": {{ labels.value | json }}
          {%- assign stk_emitted_label = true -%}
        {%- endif -%}
      {%- endfor -%}
    {%- endif -%}
    {%- if cart != blank and cart.items != blank -%}
      {%- for item in cart.items -%}
        {%- assign labels = item.variant.metafields.stk.labels -%}
        {%- if labels != blank -%}
          {%- if stk_emitted_label -%},{%- endif -%}
          "{{ item.variant.id }}": {{ labels.value | json }}
          {%- assign stk_emitted_label = true -%}
        {%- endif -%}
      {%- endfor -%}
    {%- endif -%}
  }
}
</script>
{%- comment -%} Country is also mirrored onto <body> for cart/collection contexts. {%- endcomment -%}
<script>
  document.body.setAttribute('data-stk-country', {{ localization.country.iso_code | default: 'AU' | json }});
</script>

2. assets/stk-variant-labels.js  Stk-provided · paste as-is

Create this new file in your theme’s assets folder and paste:

assets/stk-variant-labels.js
/*
 * Stk Variant Display Bridge — client label swapper.
 * Paste into the theme's assets/custom.js (luca-1 dev store first, NOT the Togs live theme).
 *
 * What it does: shows the shopper's region size label (e.g. "US 8" for a US visitor) in the
 * product variant picker, on collection quick-buy pickers, and in the cart — while the native
 * Shopify option value ("AU 12 US 8") is left untouched so checkout stays correct.
 *
 * Data source: the <script id="stk-variant-labels-data"> JSON emitted by stk-variant-labels.liquid,
 * which carries { country, variants: { <variantId>: {AU,NZ,US,CA,default} } } from the stk.* metafields.
 *
 * This is intentionally defensive: it only changes visible text whose original native option value
 * has a matching Stk label. Native inputs, option values, variant IDs and cart form data are never
 * changed. It supports Dawn, Horizon, Speed/Symmetry, and the conventional HTML radio/select markup
 * used by modern Shopify themes; a MutationObserver reapplies labels after AJAX section/cart renders.
 */
(function () {
  'use strict';

  function readData() {
    var els = document.querySelectorAll('[data-stk-variant-labels-data], #stk-variant-labels-data');
    if (!els.length) return null;

    var merged = { country: 'AU', variants: {} };
    els.forEach(function (el) {
      var parsed = null;
      try { parsed = JSON.parse(el.textContent || '{}'); } catch (e) { parsed = null; }
      if (!parsed) return;
      if (parsed.country) merged.country = parsed.country;
      Object.keys(parsed.variants || {}).forEach(function (variantId) {
        merged.variants[variantId] = parsed.variants[variantId];
      });
    });

    return Object.keys(merged.variants).length ? merged : null;
  }

  // Pick the label for a variant id given the shopper country, falling back to default/native.
  function labelFor(data, variantId, country) {
    var v = data && data.variants && data.variants[variantId];
    if (!v) return null;
    return v[country] || v[(data.country || 'AU')] || v.default || null;
  }

  function currentCountry(data) {
    return document.body.getAttribute('data-stk-country') || (data && data.country) || 'AU';
  }

  function nativeMap(data, country) {
    var byNative = {};
    Object.keys((data && data.variants) || {}).forEach(function (vid) {
      var v = data.variants[vid];
      if (v && v.default) byNative[v.default.trim()] = v[country] || v.default;
    });
    return byNative;
  }

  function textWithSwappedNative(text, byNative) {
    var out = text || '';
    Object.keys(byNative).some(function (native) {
      if (out.indexOf(native) === -1) return false;
      out = out.replace(native, byNative[native]);
      return true;
    });
    return out;
  }

  function visibleLabelForInput(input) {
    if (!input) return null;
    var wrappingLabel = input.closest('label');
    if (wrappingLabel) {
      // Horizon puts its option text in this nested span; using it preserves swatches and pills.
      return wrappingLabel.querySelector('[data-key="variant-option-text"]');
    }
    if (!input.id) return null;
    var id = window.CSS && CSS.escape ? CSS.escape(input.id) : input.id.replace(/"/g, '\\"');
    return document.querySelector('label[for="' + id + '"]');
  }

  function swapText(node, byNative) {
    if (!node) return;
    var native = node.dataset.stkNativeText || (node.textContent || '').trim();
    if (!node.dataset.stkNativeText) node.dataset.stkNativeText = native;
    var swapped = textWithSwappedNative(native, byNative);
    if (node.textContent !== swapped) node.textContent = swapped;
  }

  function swapProductLabels(data) {
    var byNative = nativeMap(data, currentCountry(data));

    document.querySelectorAll('input[type="radio"][value]').forEach(function (input) {
      var native = (input.value || '').trim();
      if (!native || !byNative[native]) return;
      swapText(visibleLabelForInput(input), byNative);
    });

    document.querySelectorAll('select option').forEach(function (option) {
      var native = (option.value || option.textContent || '').trim();
      if (!native || !byNative[native]) return;
      swapText(option, byNative);
    });

    // Horizon renders single-variant products without a radio/select control. It also repeats the
    // selected native title in the sticky add-to-cart bar. These are display-only nodes, so swapping
    // their text preserves Shopify's option value and variant identity just like the controls above.
    document.querySelectorAll(
      '[data-testid="variant-option-single-value"], [data-testid="sticky-variant-title"]'
    ).forEach(function (node) {
      swapText(node, byNative);
    });
  }

  // Rewrite cart line-item option values (Size) to the region label, matched by native value.
  function swapCartLabels(data) {
    var country = currentCountry(data);
    var byNative = nativeMap(data, country);

    document.querySelectorAll('.cart-item__variant-value, .product-option').forEach(function (node) {
      swapText(node, byNative);
    });
  }

  function applyAll() {
    var data = readData();
    if (!data) return;
    swapProductLabels(data);
    swapCartLabels(data);
  }

  document.addEventListener('DOMContentLoaded', applyAll);
  document.addEventListener('change', applyAll);
  // Common Shopify/theme variant and cart events.
  document.addEventListener('on:variant:change', applyAll);
  // Cart drawer re-renders fire these in the Speed theme.
  document.addEventListener('on:cart:change', applyAll);
  document.addEventListener('on:line-item:change', applyAll);
  document.addEventListener('variant:change', applyAll);
  document.addEventListener('cart:updated', applyAll);

  var pending = false;
  function queueApply() {
    if (pending) return;
    pending = true;
    window.requestAnimationFrame(function () { pending = false; applyAll(); });
  }
  document.addEventListener('DOMContentLoaded', function () {
    if (!window.MutationObserver || !document.body) return;
    new MutationObserver(queueApply).observe(document.body, { childList: true, subtree: true });
  });
})();

3. layout/theme.liquid  Your theme · add 2 lines

a. In the <head>, alongside your theme’s other asset_url script tags, add:

in <head>
<script src="{{ 'stk-variant-labels.js' | asset_url }}" defer="defer"></script>

b. Near the end of <body> — just before your cart drawer / cart markup — render the snippet once (this powers the cart labels):

near end of <body>
{%- render 'stk-variant-labels' -%}

4. Product variant-picker file  Your theme · add 1 line

Inside the file that renders your product’s variant picker, near the product form, render the snippet once (this powers the product-page picker labels). In Dawn this is normally sections/main-product.liquid. In Horizon 4.x it is snippets/variant-main-picker.liquid, immediately inside <variant-picker>.

in the product section
{%- render 'stk-variant-labels' -%}

The provided script supports Shopify’s Dawn, Horizon 4.x, Speed / Symmetry, and standard radio/select pickers. For another theme, load the JS once, render the snippet once in the layout and once in the product’s own variant-picker file.

Labels follow the shopper’s Shopify country

The label switches based on localization.country.iso_code. If your store only offers one country, Shopify never reports a different country, so every shopper sees the same label and nothing appears to change. To make switching possible:

  1. Shopify admin → Settings → Markets — add every country/region you sell to (e.g. add United States alongside Australia).
  2. Theme editor → Theme settings — enable the country/region selector so shoppers (and you, when testing) can switch. You can also test by adding ?country=US to a product URL.
  • Open any product page as an AU shopper — the picker should show the AU label (e.g. AU 12).
  • Switch the country selector to the US — the label should change (e.g. US 8).
  • At checkout the full native value (e.g. AU 12 US 8) still shows — that is expected and correct.
Copy this whole guide into ChatGPT, Claude, or Gemini

The button below copies everything on this page — the instructions and the full contents of both Stk files — as plain text. Paste it into your favourite AI assistant and ask it to walk you through installing it on your specific theme.


Need a hand? support@stk.now · Stock. Taken care of.

Back to Stk · support@stk.now