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.
You’re about to edit theme code — make a safety copy first so you can roll back in one click:
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.
Create this new file in your theme’s snippets folder and paste:
{%- 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>
Create this new file in your theme’s assets folder and paste:
/*
* 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 });
});
})();
a. In the <head>, alongside your theme’s other
asset_url script tags, add:
<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):
{%- render 'stk-variant-labels' -%}
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>.
{%- 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.
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:
?country=US to a product URL.AU 12).US 8).AU 12 US 8) still shows — that is expected and correct.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.