Adding custom upsells is a proven way to increase conversions in a store. Even with Shopify offering them a “You May Also Like” feature, linking a product to hand-picked upsells is powerful.
Shopify is opinionated about using Tailwind CSS on theme development. Tailwind CSS is a CSS framework that significatively reduces the time it takes to write CSS, and re-use CSS classes, so you keep CSS files small and much more perks.
So, I’ll show you how to create a custom upsell carousel using Tailwind CSS.
Pre-requisites:
– NPM installed.
– Ruby installed.
– Basic knowledge of the command line.
– A theme with jQuery if you use the slick carousel (I’ll explain this later).
Step 1:
Add a metafield of type product->List of products in the product pages (settings->metafields) and name it as you wish. In my case, the namespace and key for that metafield are tonybundles.upsells.
Go to a product and select 5 products in the metafield you just created.
Create a copy of the theme you are modifying. Do it in the Shopify admin dashboard
Step 2 (optional):
If you have not installed the Shopify CLI in your system, open a command shell and run:
gem install shopify-cli
Step 3:
In a command shell, make a directory, change the directory to the created directory, and login into the Shopify store you wish to modify. Replace https://xyz.myshopify.com with the URL of the store you want to modify
mkdir brand_name cd brand_name shopify login --store https://xyz.myshopify.com
Again, in the command shell, pull the theme you duplicated in the Shopify admin dashboard to your computer:
shopify pull theme
With this, in the folder you created (brand_name), you’ll have all the files for the theme you are going to work on.
Run the following command:
shopify theme serve
Open the URL 127.0.0.1:9292 in your favorite browser, and we are ready to start.
You can check the URL 127.0.0.1:9292 every time you edit the theme locally and you want to see how the changes look.
Step 4:
Add the following script before the </body> tag in theme.liquid (your local copy of the theme you just pulled).
<script src="https://cdn.tailwindcss.com"></script>
This script will load the CSS classes Tailwind CSS has to offer. It’s a big file, but we won’t include this script in production.
Find the {{ product.description }} variable in the product page, i.e., product-template.liquid for legacy themes or main-product.liquid for OS 2.0 themes, and add the following code after the variable {{ product.description }}:
Note: We are using jQuery because we are using the slick carousel. If you want to use vanilla js, consider using another slider, i.e., flickity or a native slider with overflow: scrollNote II: Remember you need to do this in the local theme you pulled before.Note III: Replace tonybundles.upsells for the id of the metafield you created in a previous step of this guide.
{% comment %} We make sure we only load a slider if the product got the metafield set up. {% endcomment %} {% if product.metafields.tonybundles.upsells.value %} <div class="w-full"> <h2 class="text-center font-black">Bundle with</h2> <div class="tony-bundles-wrapper"> {% comment %} Replace tonybundles.upsells for the id of the metafield you created in a previous step of this guide. {% endcomment %} {% for producti in product.metafields.tonybundles.upsells.value %} {% if product.available %} <div data-bundle-form="{{ forloop.index }}"> <div class="flex items-center justify-around"> <div> <span class="font-black"> {{ producti.title }} </span> <div class="italic"> {% if producti.first_available_variant.compare_at_price > producti.first_available_variant.price %} <s class=”bundle-compare-price”>{{ producti.first_available_variant.compare_at_price | money }}</s> {% endif %} <span class=”bundle-final-price”>{{ producti.first_available_variant.price | money }}</span> </div> </div> {% if producti.first_available_variant.featured_image.src %} <img class=’bundle-image’ loading="lazy" src="{{ producti.first_available_variant.featured_image | img_url }}"> {% else %} <img class=’bundle-image’ loading="lazy" src="{{ producti.featured_image | img_url }}"> {% endif %} </div> {% if producti.variants.size > 1 %} <div> <select name="bundle_id" class="mt-4" data-reference="{{ forloop.index }}"> {% for varianti in producti.variants %} <option data-qty="{{ varianti.inventory_quantity }}" data-compare="{{ varianti.compare_at_price }}" data-price="{{ varianti.price | money}}" data-img="{% if varianti.featured_image.src %}{{ varianti.featured_image | img_url }}{% else %}no_img{% endif %}" {% if varianti.id == producti.first_available_variant.id %} selected {% endif %} value="{{ varianti.id }}" {% unless varianti.available %} disabled {% endunless %} > {{ varianti.title }} </option> {% endfor %} </select> </div> {% else %} <input type="hidden" name="bundle_id" value="{{ producti.first_available_variant.id }}"> {% endif %} <div class="flex fle-col items-start lg:flex-row lg:items-center"> <label class="font-black">Quantity</label> <input class='m-0 lg:ml-4 w-1/12' type="number" max="{{ producti.first_available_variant.inventory_quantity }}" min="1" value="1" name="bundle_quantity" > </div> <div class="flex flex-col"> <span class='mb-4 text-rose-900 hidden'> There was an error adding this item to your cart. Please try again. </span> <span class='mb-4 text-rose-900 hidden'> Added to bundle. </span> <button type="button" class="btn mt-4" name="submit_bundle" data-reference="{{ forloop.index }}">Add To Bundle</button> </div> </div> {% endif %} {% endfor %} </div> </div> <script> $(document).ready(function(){ $('.tony-bundles-wrapper').slick({ dots: true, infinite: true, speed: 500, fade: true, cssEase: 'linear', arrows: true }); $(“select[name=’bundle_id’]”).on(‘change’, function(){ const form = $(this).parents(“[data-bundle-form]”); const src = $(this).children(‘option:selected’).attr(“data-img”); const price = $(this).children(‘option:selected’).attr(“data-price”); const compare = $(this).children(‘option:selected’).attr(“data-compare”); if(src != “no_img”) form.find(“.bundle-image”).attr(“src”, src); form.find(‘.bundle-final-price’).text(price); compare < price ? form.find(”bundle-compare-price”).text(compare) : form.find(”bundle-compare-price”).text(“”) }) $("[name=submit_bundle]").on('click', function(){ /* This is a way to select the form that is associated with the upsell added to the cart. Get its variant id and qty */ const form = $(this).parents(“[data-bundle-form]”); const id = form.find("[name=bundle_id]").val(); const qty = form.find("input[name=bundle_quantity]").val(); /* This is checking if the form has a select element. If it does, it is getting the quantity of the selected option and then subtracting 1 from it. If the quantity is 0, it is disabling the option. */ if(form.find('[name="bundle_id"]').is("select")){ var prevQty = form.find('[name="bundle_id"]').children("option:selected").attr("data-qty"); form.find('[name="bundle_id"]').children("option:selected").attr("data-qty", prevQty-1); if(prevQty-1 <= 0) form.find('[name="bundle_id"]').children("option:selected").prop("disabled", true); } /* This is setting the max attribute of the qty selector element to the previous quantity minus 1. */ form.find("input[name=bundle_quantity]").attr("max", prevQty-1); /* This is checking if the quantity of the selected option is less than or equal to 0. If it is, it is disabling the qty selector element element. */ if(prevQty-1 <= 0) form.find("input[name=bundle_quantity]").children("input").prop("disabled", true); /* This is the code that is used to add the bundle to the cart. */ jQuery.ajax({ type: 'POST', url: '/cart/add.js', data: { quantity: qty, id: id }, dataType: 'json', success: function() { $(".bundle_success").show; $(".bundle_error").hide(); /* Optional * window.location.href = ‘/cart’; //Uncomment this to redirect to the cart after adding to the bundle. * or…. //Add here the code to trigger the cart drawer * */ }, error: () => { $(".bundle_success").hide(); $(".bundle_error").show(); } }); }) }) </script> {% endif %}
Step 5:
In your local theme.liquid file, add the following before the </body> tag:
{% if template contains “product” and product.metafields.tonybundles.upsells.value %} <link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/npm/[email protected]/slick/slick.css"> <script type="text/javascript" src="//cdn.jsdelivr.net/npm/[email protected]/slick/slick.min.js" defer> </script> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.8.1/slick-theme.css" integrity="sha512-6lLUdeQ5uheMFbWm3CP271l14RsX1xtx+J5x2yeIDkkiBpeVTNhTqijME7GgRKKi6hCqovwCoBTlRBEC20M8Mg==" crossorigin="anonymous" referrerpolicy="no-referrer" > {% endif %}
This will initialize the slick slider for all the products with the metafield active. Feel free to skip the previous step if your theme already installed the slick slider or if you are using another slider, inject it there.
Step 6:
Now, visit 127.0.0.1:9292 and navigate to the product with the metafields you set up in the first steps of this guide; you should see a carousel with upsells under the product description.
Every website and theme is different, so make sure to add Tailwind CSS rules and/or your own CSS to make it look great.
Carousel features:
- Each upsell is a slide.
- Can choose variants and quantities.
- Image and price changes depending on the variant selected.
- Can add the variant to the cart directly from the carousel.
- Inform the user when an upsell has been added to the cart or if there was an error in adding a variant.
- Disable not available variants.
- Don’t show slides with a product unavailable.
- Keep track of quantities dynamically when an upsell is added, so you can’t add an unavailable product.
Ideas to extend the carousel:
- Add a product’s images slider inside each product slide.
- Wrap each slide around a <a> tag that links to the product.
- Use overflow: scroll on .tony-bundles-wrapper so the carousel won’t require JS.
- Make the carousel a popup that would show after adding a product to the cart.
- Use collections metafields instead of products metafields for stores with a large catalog.
Step 7 (optional):
Depending on your needs, you may want to open the cart drawer after adding an upsell or redirect directly to the cart. You can do that using the success function of jQuery.ajax({})
Once you are happy with the look and functionality of the carousel, let’s generate the Tailwind CSS rules.
Step 8:
Create a file named tailwind.config.js in the root directory of the brand_name folder you created and paste the following:
NOTE: DON’T FORGET TO REPLACE ‘./sections/product-template.liquid’ FOR THE PATH OF THE LIQUID FILE WHERE YOU ADDED THE UPSELLS CARROUSEL.
purge: { enabled: true, content: [ './sections/product-template.liquid', ], },
Create a folder in the root directory of brand_name called sources and add a subfolder named CSS.
Inside the folder CSS (/brand_name/sources/css), create a file named application-upsells.css and fill it with the following:
/* purgecss start ignore */ @tailwind base; @tailwind components; /* purgecss end ignore */@tailwind utilities;
Now, install Tailwind CSS:
npm install tailwindcss
And Generate the Tailwind CSS rules:
npx tailwindcss build -i src/css/application-upsells.css -o assets/application-upsells.css
This will generate a CSS file (brand_name/assets/application-upsells.css) with ONLY the rules you used, making this file fairly small.
Step 9 (final):
Remove the following line you added on the local copy of theme.liquid:
<script src="https://cdn.tailwindcss.com"></script>
Inside the if statement you added to load the slick slider, add the following to load the Tailwind CSS final styles:
{{ 'application-upsells.css' | asset_url | stylesheet_tag }}
And voila! You just made a upsell carousel made with Tailwind CSS. You can go to any product and set up the appropriate metafield to choose upsells to offer per product.
Don’t forget to push the changes you made to Shopify with the following command:
shopify theme push
If you enjoyed this post, I’d be very grateful if you’d help it spread by emailing it to a friend, or sharing it on Twitter or Facebook. Thank you!
you may also like this posts