IWIK : I Wish I Knew
Table of content
Custom variant size selector
Introduction
For a client's e-commerce project I had the opportunity to work with Shopify. It was my first time using it and while the overall experience was great, I came across several use cases where I thought beforehand that it would be simpler.
Today's subject is a simple looking feature that is so common that it can be overlooked when estimating the amount of work / customization you need in a product page. A variant selector and, more precisely, what the next lines describes, a size chart selector. Using the Debut theme* where the default style for this element is a dropdown list.
*The Liquid markup and Javascript used in this tutorial are based on the Debut theme.
Here's the result we want to obtain :
Now, it is a common display that's on a lot of Shopify based e-commerce websites but when it's your first time, the new terms might get confusing. I am perfectly aware that it is not the trickiest feature but having an article like this could have helped me save a few hours of research.
Product configuration
To a new or existing product add a variant named "size" (name is important here, if you want another name be sure to replace "size" in the example code with your custom name) with multiple options :
If you visit this product page you should be able to see a size selector automatically generated, depending on the theme it may match your needs but if you are reading this, I guess not exactly.
Liquid template markup
We're not going to reinvent the wheel and make good use of what's already in place.
Edit your theme code and take a look inside /sections/product-template.liquid
:
This code does one thing : for every variant that exists for a product, it generates a dropdown selector. We are going to update this for loop in a way that :
- Our size selector is a specific case with a different markup
- The dropdown select, the default result
That way it won't break any existing or future variant selector.
The liquid code, inside the product.options_with_values
for loop :
<div class="selector-wrapper js product-form__item">
<p>{{ option.name }}</p>
{% if option.name == "Size" %}
{% assign index = forloop.index %}
<div class="size-selector">
<div class="size-selector__list">
{% for value in option.values %}
{% assign sizeWords = value | split: ' ' %}
{% capture sizeLetters %}{% for word in sizeWords %}
{{ word | slice: 0 }}
{% endfor %}{% endcapture %}
<div class="size-selector__item">
<input
class="single-option-selector-{{ section.id }}"
id="size-{{ forloop.index }}" type="radio" name="size"
value="{{ value | escape }}"
data-index="option{{index}}"
{% if option.selected_value == value %} checked="true"{% endif %} />
<label for="size-{{ forloop.index }}">{{ sizeLetters }}</label>
</div>
{% endfor %}
</div>
</div>
{% else %}
// The original code without the .selector-wrapper div
{% endif %}
</div>
You should obtain something that looks like this :
Some explanations :
- Wrap everything in a new
.selector-wrapper
class - The IF condition on line 167 matches the name we gave to our product variant (to be replaced with a custom name if different)
- Instead of a
<select>
we're now using radio inputs, one for each of our "size" - Bonus : only the first letter of each word you use as a size will appear on your selector (ex: Small → S, X Large → XL, etc...)
Basic styling
Here is the CSS used to obtain the final look (location depends how you manage your css, default in assets/theme.css
)
.size-selector {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 60px;
}
.size-selector input {
display: none;
}
.size-selector p {
font-size: 18px;
line-height: 57px;
letter-spacing: 0.56px;
}
.size-selector .selector-label:hover {
text-decoration: underline;
}
.size-selector__list {
display: flex;
}
.size-selector__item:hover label,
.size-selector__item input[checked] ~ label {
border-color: #2A2726;
}
.size-selector__item label {
display: flex;
align-items: center;
justify-content: center;
height: 32px;
min-width: 32px;
margin-right: 16px;
border-radius: 50%;
border: 1px solid transparent;
font-size: 14px;
line-height: 18px;
text-align: center;
}
Make sure to save all the recent changes. Visit your product page again, refresh and start playing with the size selector.
Proper behavior with Javascript
It works without extra Javascript code because it uses the same class single-option-selector-{{ section.id }}
. The theme should handle radio and checkbox automatically.
If you look at the url you can see a change in the variant ID param. That means it's working but as you can see, the "active" size doesn't get updated. To fix that we are going to need some Javascript to :
- Target our
.size-selector
class - Toggle the checked attribute when the current selected option changes
Find theme.Product
in assets/theme.js
In the selectors object add :
this.selectors = {
...,
productSizes: '.size-selector',
}
Scroll down until you find a function named _initVariants
In the options add :
var options = {
...
productSizesSelector: this.selectors.productSizes,
};
Find slate.variants
in assets/theme.js
In the constructor function named Variants add :
this.productSizesSelector = this.container.querySelector(options.productSizesSelector);
if (this.productSizesSelector)
this.singleOptionsSize = this.productSizesSelector.querySelectorAll(options.singleOptionSelector);
The first line targets the class selector we have defined in the previous steps. After checking for the existence of a size selector element in the page, it selects all the radio inputs options.
Scroll down to find a function named _onSelectChange
Under the line this.currentVariant = variant
add :
if (event.currentTarget.getAttribute('name') == 'size') {
this.singleOptionsSize.forEach(function(option) {
option.removeAttribute('checked');
});
event.currentTarget.setAttribute('checked', true);
}
On every variant change, if that concern our size selector :
- Remove the checked attribute from all the options
- Set the selected option as checked
Be sure to save everything once again, refresh and you should obtain the desired behavior. If it's not the case, double check that :
- The
.size-selector
class (without the.
) is present in the loop markup - The IF condition in the last step
== 'size'
is correct (based on the name of your variant)
Insights
As I explained earlier a variant selector is only one of the concepts that beginners have to apprehend when learning Shopify.
Most of the pain points I experienced are mostly around the use of Javascript for transitions and animations and might be the subject for another article.
Please let me know if you experience any difficulty creating your own version of the size selector.
Shopify custom variant with images
@adhendo in the comments asked how I would handle images.
It's actually fast to implement.
Liquid implementation and explanations
In the Shopify Product page administration :
- Click More options > edit options : add another option (I’ll use fabric in my example)
- Add variant : Fill only the fabric field
- Create and name the images you want with the same name that the variant value (ex:
- Variant : red -> Image : fabric_red.jpg
- Variant : Dark blue -> Image : fabric_dark-blue.jpg
- …)
- Upload your images in the /assets folder (Go to "Online store" on the left sidebar, Actions > edit code > find the assets folder > add asset)
If you look again at my liquid code from the classic variant you will notice on the 3rd line :
{% if option.name == "Size" %}{% endif %}
Instead of this we would use
{% if option.name == "Fabric" %}{% endif %}
I used a slider library named glide (hence the class names and structure) but you are free to use any style/library later.
Here is my implementation. Explanations are just below this code block.
{% if option.name == « Fabric » %}
<div class="fabric-selector">
<label class="selector-label">Pick a fabric</label>
<div class="glide">
<div class="glide__track" data-glide-el="track">
{% assign index = forloop.index %}
<div class="glide__slides">
{% for value in option.values %}
<div class="glide__slide">
<img src="{{ value | handleize | append: '.jpg' | prepend: 'fabric_' | strip | asset_url }}" alt="{{ option.name }}" />
<input
class="single-option-selector-{{ section.id }}"
id="color-{{ forloop.index }}" type="radio" name="color"
value="{{ value | escape }}"
data-index="option{{index}}"
{% if option.selected_value == value %} checked="true"{% endif %} />
<label for="color-{{ forloop.index }}">{{ value }}</label>
</div>
{% endfor %}
</div>
</div>
<div class="glide__arrows" data-glide-el="controls">
<span class="glide__arrow glide__arrow--left {% if option.values.size < 2 %}hidden{% endif %}" data-glide-dir="<">
{% include 'nw-chevron-left' %}
</span>
<div class="fabric-selector__data">
<p class="fabric-selector__data-name">{{ option.values[0] }}</p>
</div>
<span class="glide__arrow glide__arrow--right {% if option.values.size < 2 %}hidden{% endif %}" data-glide-dir=">">
{% include 'nw-chevron-right' %}
</span>
</div>
</div>
</div>
{% elseif option.name == "Size" %}...{% endif %}
Line 10 is where the image gets called. What is happening is details :
handleize : if your fabric name has spaces or uppercase it will be transformed to a slug like string for a better image name (ex: Dark grey -> fabric_dark-grey)
append : Because all my images are .jpg (but any image format would work)
prepend : I prefer to use a prefix for naming my images
asset_url : Get that image from the asset folder
The Javascript part
The implementation will vary depending on how you want to display the images (basic list of elements or in a slider).
For the basic to run properly, follow the steps for the size-selector and change the variable names accordingly (fabric-selector in my case).
Thank you for reading 🙏
Top comments (11)
Hi Stephen, for some reason when I embed the code does not do not seem to work
Hi, can you provide more details or a link to a gist/repo about what doesn't work ?
yes, here you go gist.github.com/longdasian/d2963e4...
Will really appreciate your help!
I looked into your liquid code (tested in a demo store) and it works so this part is sure, let's check the CSS and JS part !
Can you then check your theme.js file against this gist (gist.github.com/stephenrichard/f88...) which is the article implementation on a debut theme.
Let me know your progress or provide your theme.js if you can't find a solution.
I also embedded the CSS code into theme.css and it still shows up like this
docs.google.com/document/d/14MWox5...
Unfortunately I can't do much without seeing some code. Can you share the url of your shop so I can look up the css and js ? Feel free to contact me here : stephen.richard44@gmail.com if you want to share code or urls.
ok i am going to email you
Any insight how to do this but display a thumbnail image instead of ‘small, medium, large’? For example if you had color variants with images attached to each variant how could you access these and use them as the selectors? Thansk!
Hello, I know it might be too late for you but I updated the article with another example of custom variant with images.
Feel free to read it if you are still interested and thank you for asking. I hope it can be useful for future readers.
This is such a good tutorial. I have used this a couple of times now to add colour swatches and size options to products and it works really well!