DEV Community

Cover image for Shopify - Create a custom variant selector
Stephen Richard
Stephen Richard

Posted on • Edited on

Shopify - Create a custom variant selector

IWIK : I Wish I Knew

Table of content

  1. Custom variant size selector
  2. Shopify custom variant with images

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 :

output

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 :

variants

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:

before

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

You should obtain something that looks like this :
after

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

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) {
    option.removeAttribute('checked');
  });
  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)

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 :

  1. Click More options > edit options : add another option (I’ll use fabric in my example)
  2. Add variant : Fill only the fabric field
  3. Create and name the images you want with the same name that the variant value (ex:
  4. Variant : red -> Image : fabric_red.jpg
  5. Variant : Dark blue -> Image : fabric_dark-blue.jpg
  6. …)
  7. 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 %}
Enter fullscreen mode Exit fullscreen mode

Instead of this we would use

{% if option.name == "Fabric" %}{% endif %}
Enter fullscreen mode Exit fullscreen mode

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

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)

Collapse
 
dasian_long_56b27e7dcb82c profile image
Dasian Long

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

Collapse
 
stephenrichard profile image
Stephen Richard

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

Collapse
 
dasian_long_56b27e7dcb82c profile image
Dasian Long
Thread Thread
 
dasian_long_56b27e7dcb82c profile image
Dasian Long

Will really appreciate your help!

Thread Thread
 
stephenrichard profile image
Stephen Richard

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 (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.

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
docs.google.com/document/d/14MWox5...

Thread Thread
 
stephenrichard profile image
Stephen Richard

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.

Thread Thread
 
dasian_long_56b27e7dcb82c profile image
Dasian Long

ok i am going to email you

Collapse
 
adhendo profile image
adhendo

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!

Collapse
 
stephenrichard profile image
Stephen Richard

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.

Collapse
 
natalialb profile image
natalialb

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!