Skip to content

Instantly share code, notes, and snippets.

@panoply
Last active September 6, 2023 09:16
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save panoply/ba970dfc96f0c0b8c9e4d025b655c4d6 to your computer and use it in GitHub Desktop.
Save panoply/ba970dfc96f0c0b8c9e4d025b655c4d6 to your computer and use it in GitHub Desktop.
Shopify Size Charts

Shopify Size Charts

This is one way to produce a table based size chart in Shopify leveraging Metafields, Locales and some basic Liquid. The data for product size charts (metafields) are applied via an ERP I developed so for example sake I have created a Flems which allows folks to generate the JSON for each product.

The example size_charts.liquid is assumed to be a snippet. The measurements variable is passed in via render tag arguments within a section - For example, let's say your section is called product_information.liquid - then you would include the snippet like this:

{% render 'size_charts', measurements: product.metafields.data.measurements.value %}

Flems

Visit this flems

Locales

This approach requires entries to be populated into locales. The attached en.json snippet is an extract I apply for one of our brands.

Global Metafields

In order to get the most from the otherwise poor performance of Shopify stores, we apply some additional logic using Global metafields. See the snippet at global_metafields.json which is used to describe key values.

Product Size charts

The product_example_metafield.json snippet is the output result and used to render the size charts.

Component

For some additonal context I have included a minimal example of a stimulus controller which is responsible for switching between metric and imperial sizing. This is totally optional.

bare in mind that some internal logic exists in the component, like customer.session.countryCode which is an internal data reference to customers that automaticaly loads the location, so omit that.

Results

The size charts are responsive and use different naming conventions in different screen sizes.

Desktop

Screenshot 2022-08-24 at 00 38 40

Mobile

Screenshot 2022-08-24 at 00 39 11

Example Toggle

Screen.Recording.2022-08-24.at.00.48.41.mov
import { LiteralUnion } from 'type-fest';
import { Controller } from '@hotwired/stimulus';
import * as customer from 'application/customer';
export class Sizing extends Controller {
/**
* Stimulus Values
*/
static values = {
measurementType: String,
metricTolerence: String,
imperialTolerence: String
};
/**
* Stimulus Targets
*/
static targets = [
'imperial',
'metric',
'weight',
'measurement',
'tolerence'
];
/**
* Stimulus Initialize
*/
connect () {
this.measurementTypeValue = customer.session.countryCode !== 'US'
? 'metric'
: 'imperial';
}
get sizeUnit () {
if (this.measurementTypeValue === 'metric') {
this.imperialTarget.classList.remove('active');
this.metricTarget.classList.add('active');
this.tolerenceTarget.innerHTML = this.metricTolerenceValue;
return 'cm';
} else if (this.measurementTypeValue === 'imperial') {
this.metricTarget.classList.remove('active');
this.imperialTarget.classList.add('active');
this.tolerenceTarget.innerHTML = this.imperialTolerenceValue;
return '″';
}
return 'cm';
}
toggle (event: Event) {
event.preventDefault();
if (event.target instanceof HTMLElement) {
if (!event.target.hasAttribute('data-sizing-target')) return;
if (event.target.classList.contains('active')) event.target.classList.remove('active');
this.measurementTypeValue = event.target.getAttribute('data-sizing-target');
for (const node of this.measurementTargets) {
const attr = node.getAttribute(`data-${this.measurementTypeValue}`).trim();
if (attr.length > 0) {
if (node.hasAttribute('data-unit')) {
if (this.measurementTypeValue === 'imperial') {
node.innerHTML = `${attr} <small>LBS</small>`;
} else {
node.innerHTML = `${attr}kg`;
}
} else {
node.innerHTML = `${attr}${this.sizeUnit}`;
}
}
}
}
}
/* -------------------------------------------- */
/* TYPES */
/* -------------------------------------------- */
/**
* Stimulus: Imperial measurement
*/
imperialTarget: HTMLElement;
/**
* Stimulus: Metric measurement
*/
metricTarget: HTMLElement;
/**
* Stimulus: tolerence text
*/
tolerenceTarget: HTMLElement;
/**
* Stimulus: measurement target
*/
measurementTargets: HTMLElement[];
/**
* Stimulus: measurement type, ie: imperial or metric
*/
measurementTypeValue: LiteralUnion<'metric' | 'imperial', string>;
/**
* Stimulus: Metric measurement tolerance label
*/
metricTolerenceValue: string;
/**
* Stimulus: Imperial measurement tolerance label
*/
imperialTolerenceValue: string;
}
{
"product": {
"sizing": {
"group": "Size",
"arm": "Arm",
"chest": "½ Chest",
"bust": "Bust",
"back": "Back",
"fit_measurements": "Measurements",
"imperial": {
"title": "Imperial",
"tolerence": "All Measurements have a 0.60 inch Tolerence"
},
"metric": {
"title": "Metric",
"tolerence": "All Measurements have a 1 ½ cm Tolerence"
},
"x_small": {
"title": "X Small",
"abbreviation": "xs"
},
"small": {
"title": "Small",
"abbreviation": "sm"
},
"medium": {
"title": "Medium",
"abbreviation": "md"
},
"large": {
"title": "Large",
"abbreviation": "lg"
},
"x_large": {
"title": "X Large",
"abbreviation": "xl"
},
"xx_large": {
"title": "XX Large",
"abbreviation": "xxl"
}
}
}
}
[
[
"x_small.title",
"x_small.abbreviation"
],
[
"arm",
"x_small"
],
[
"back",
"x_small"
],
[
"chest",
"x_small"
],
[
"small.title",
"small.abbreviation"
],
[
"arm",
"small"
],
[
"back",
"small"
],
[
"chest",
"small"
],
[
"medium.title",
"medium.abbreviation"
],
[
"arm",
"medium"
],
[
"back",
"medium"
],
[
"chest",
"medium"
],
[
"large.title",
"large.abbreviation"
],
[
"arm",
"large"
],
[
"back",
"large"
],
[
"chest",
"large"
],
[
"x_large.title",
"x_large.abbreviation"
],
[
"arm",
"x_large"
],
[
"back",
"x_large"
],
[
"chest",
"x_large"
],
[
"xx_large.title",
"xx_large.abbreviation"
],
[
"arm",
"xx_large"
],
[
"back",
"xx_large"
],
[
"chest",
"xx_large"
]
]
{
"arm": {
"x_small": {
"cm": 56,
"in": 22
},
"small": {
"cm": 57,
"in": 22
},
"medium": {
"cm": 58,
"in": 22
},
"large": {
"cm": 59,
"in": 23
},
"x_large": {
"cm": 60,
"in": 23
},
"xx_large": {
"cm": 61,
"in": 24
}
},
"back": {
"x_small": {
"cm": 79,
"in": 31
},
"small": {
"cm": 81,
"in": 31
},
"medium": {
"cm": 82,
"in": 32
},
"large": {
"cm": 84,
"in": 33
},
"x_large": {
"cm": 85,
"in": 33
},
"xx_large": {
"cm": 87,
"in": 34
}
},
"chest": {
"x_small": {
"cm": 59,
"in": 23
},
"small": {
"cm": 61,
"in": 24
},
"medium": {
"cm": 63,
"in": 24
},
"large": {
"cm": 65,
"in": 25
},
"x_large": {
"cm": 67,
"in": 26
},
"xx_large": {
"cm": 69,
"in": 27
}
}
}
<table>
<thead>
<tr>
<th>
{{- 'product.sizing.group' | t -}}
</th>
<th>
{{- 'product.sizing.arm' | t -}}
</th>
<th>
{{- 'product.sizing.back' | t -}}
</th>
<th>
{{- 'product.sizing.chest' | t -}}
</th>
</tr>
</thead>
<tbody>
{%- tablerow item in shop.metafields.product.size_charts cols: 4 -%}
{%- if item[0] contains '.title' -%}
<div class="py-1 px-2" scope="row">
<span class="d-none d-lg-block">
{{- 'product.sizing.' | append: item[0] | t -}}
</span>
<span class="d-block d-lg-none">
{{- 'product.sizing.' | append: item[1] | t | upcase -}}
</span>
</div>
{%- else -%}
<div
class="lower py-1 px-2"
data-sizing-target="measurement"
data-metric="{{ measurement[item[0]][item[1]].cm }}"
data-imperial="{{ measurement[item[0]][item[1]].in }}">
{{- measurement[item[0]][item[1]].cm | append: 'cm' }}
</div>
{%- endif -%}
{%- endtablerow -%}
</tbody>
</table>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment