DEV Community

Cover image for Shopify - Create a custom variant selector

Shopify - Create a custom variant selector

Stephen Richard
Front-end developer / Freelance Looking for a job in Tokyo 🇯🇵
・5 min read

IWIK : I Wish I Knew


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>{{ }}</p>
      {% if == "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">
               class="single-option-selector-{{ }}" 
               id="size-{{ forloop.index }}" type="radio" name="size" 
               value="{{ value | escape }}" 
               {% if option.selected_value == value %} checked="true"{% endif %} />
              <label for="size-{{ forloop.index }}">{{ sizeLetters }}</label>
            {% endfor %}
    {% else %}
        // The original code without the .selector-wrapper div
    {% endif %}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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-{{ }}. 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',

Enter fullscreen mode Exit fullscreen mode

Scroll down until you find a function named _initVariants

In the options add :

var options = {
  productSizesSelector: this.selectors.productSizes,
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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) {
  event.currentTarget.setAttribute('checked', true);
Enter fullscreen mode Exit fullscreen mode

On every variant change, if that concern our size selector :

  1. Remove the checked attribute from all the options
  2. 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)


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.

Discussion (8)

dasian_long_56b27e7dcb82c profile image
Dasian Long

Hi Stephen, for some reason when I embed the code does not do not seem to work

stephenrichard profile image
Stephen Richard Author

Hi, can you provide more details or a link to a gist/repo about what doesn't work ?

dasian_long_56b27e7dcb82c profile image
Thread Thread
dasian_long_56b27e7dcb82c profile image
Dasian Long

Will really appreciate your help!

Thread Thread
stephenrichard profile image
Stephen Richard Author

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 !

  • First of all, do you have variants setup for your product ?
  • Can you see the url change when you press on the different sizes ? (If yes it's already working, the issue should come from the javascript file)

Can you then check your theme.js file against this gist ( 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.

Thread Thread
dasian_long_56b27e7dcb82c profile image
Dasian Long

I also embedded the CSS code into theme.css and it still shows up like this

Thread Thread
stephenrichard profile image
Stephen Richard Author

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 : if you want to share code or urls.

Thread Thread
dasian_long_56b27e7dcb82c profile image
Dasian Long

ok i am going to email you

Forem Open with the Forem app