Where you can achieve what your competitors can’t


Product Add-ons in Shopify

This custom code is completely free to use, but please note it does not include 1-on-1 technical support. If you need a bespoke, done-for-you solution to customize your Shopify store, my team is here to help! Head to my channel homepage and use the link in the trailer to reach out.

{% comment %}
<!-- Designed by ONHOW Studio YouTube Channel - Anas El Medlaoui -->
{% endcomment %}

<style>
.onhow-studio-upsell-widget {
  margin: 2rem 0;
  padding: 1rem 0;
  font-family: inherit;
  width: 100%;
  box-sizing: border-box;
}
.onhow-studio-upsell-heading {
  font-size: calc(var(--font-heading-scale, 1) * 1.2rem);
  margin-bottom: 1.25rem;
  font-weight: 600;
  color: inherit;
  letter-spacing: inherit;
}
.onhow-studio-upsell-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 1.25rem;
}
.onhow-studio-upsell-card {
  display: flex;
  flex-direction: column;
  border: 1px solid rgba(var(--color-foreground, 0, 0, 0), 0.08);
  border-radius: min(var(--border-radius, 8px), 8px);
  overflow: hidden;
  background: rgb(var(--color-background, 255, 255, 255));
  transition: box-shadow 0.2s ease, transform 0.2s ease;
  height: 100%;
}
.onhow-studio-upsell-card:hover {
  box-shadow: 0 4px 12px rgba(var(--color-foreground, 0, 0, 0), 0.05);
}
.onhow-studio-upsell-image-link {
  display: block;
  position: relative;
  width: 100%;
  padding-top: 125%;
  background-color: rgba(var(--color-foreground, 0, 0, 0), 0.03);
  overflow: hidden;
}
.onhow-studio-upsell-image,
.onhow-studio-upsell-placeholder {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.3s ease;
}
.onhow-studio-upsell-card:hover .onhow-studio-upsell-image {
  transform: scale(1.03);
}
.onhow-studio-upsell-placeholder {
  fill: rgba(var(--color-foreground, 0, 0, 0), 0.2);
}
.onhow-studio-upsell-info {
  padding: 1rem;
  display: flex;
  flex-direction: column;
  flex-grow: 1;
}
.onhow-studio-upsell-title {
  font-size: 0.95rem;
  color: inherit;
  text-decoration: none;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  margin-bottom: 0.5rem;
  line-height: 1.3;
  font-weight: 500;
}
.onhow-studio-upsell-price {
  font-size: 0.9rem;
  margin-bottom: 1rem;
  font-weight: 500;
  color: inherit;
}
.onhow-studio-upsell-price-compare {
  color: rgba(var(--color-foreground, 0, 0, 0), 0.6);
  font-size: 0.8rem;
  margin-left: 0.4rem;
}
.onhow-studio-upsell-form {
  margin-top: auto;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.onhow-studio-upsell-variant-select {
  width: 100%;
  padding: 0.5rem;
  border: 1px solid rgba(var(--color-foreground, 0, 0, 0), 0.1);
  border-radius: min(var(--border-radius, 4px), 4px);
  background: transparent;
  font-size: 0.85rem;
  font-family: inherit;
  color: inherit;
  appearance: auto;
  cursor: pointer;
}
.onhow-studio-upsell-button {
  width: 100%;
  padding: 0.6rem;
  background-color: rgb(var(--color-button, 0, 0, 0));
  color: rgb(var(--color-button-text, 255, 255, 255));
  border: none;
  border-radius: min(var(--border-radius, 4px), 4px);
  cursor: pointer;
  font-weight: 600;
  font-size: 0.85rem;
  text-align: center;
  text-transform: inherit;
  letter-spacing: inherit;
  transition: opacity 0.2s ease;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: inherit;
}
.onhow-studio-upsell-button:hover:not(:disabled) {
  opacity: 0.85;
}
.onhow-studio-upsell-button:disabled {
  background-color: rgba(var(--color-foreground, 0, 0, 0), 0.1);
  color: rgba(var(--color-foreground, 0, 0, 0), 0.4);
  cursor: not-allowed;
}
@media screen and (max-width: 768px) {
  .onhow-studio-upsell-grid {
    display: flex;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    padding-bottom: 1rem;
    -webkit-overflow-scrolling: touch;
    margin-right: -1.5rem;
    padding-right: 1.5rem;
  }
  .onhow-studio-upsell-grid::-webkit-scrollbar {
    height: 4px;
  }
  .onhow-studio-upsell-grid::-webkit-scrollbar-thumb {
    background-color: rgba(var(--color-foreground, 0, 0, 0), 0.15);
    border-radius: 4px;
  }
  .onhow-studio-upsell-card {
    flex: 0 0 160px;
    scroll-snap-align: start;
  }
}
</style>

{% if product.metafields.custom.upsell_products and product.metafields.custom.upsell_products.value != blank %}
  <div class="onhow-studio-upsell-widget">
    <h3 class="onhow-studio-upsell-heading">Pairs well with</h3>
    <div class="onhow-studio-upsell-grid">
      {% for upsell_product in product.metafields.custom.upsell_products.value %}
        {% if upsell_product.available %}
          <div class="onhow-studio-upsell-card">
            <a href="{{ upsell_product.url }}" class="onhow-studio-upsell-image-link">
              {% if upsell_product.featured_image %}
                <img
                  src="{{ upsell_product.featured_image | image_url: width: 400 }}"
                  alt="{{ upsell_product.title | escape }}"
                  class="onhow-studio-upsell-image"
                  loading="lazy"
                >
              {% else %}
                {{ 'product-1' | placeholder_svg_tag: 'onhow-studio-upsell-placeholder' }}
              {% endif %}
            </a>
            <div class="onhow-studio-upsell-info">
              <a href="{{ upsell_product.url }}" class="onhow-studio-upsell-title">{{ upsell_product.title }}</a>
              <div class="onhow-studio-upsell-price">
                {% if upsell_product.compare_at_price > upsell_product.price %}
                  <span>{{ upsell_product.price | money }}</span>
                  <s class="onhow-studio-upsell-price-compare">{{ upsell_product.compare_at_price | money }}</s>
                {% else %}
                  <span>{{ upsell_product.price | money }}</span>
                {% endif %}
              </div>
              <form class="onhow-studio-upsell-form">
                {% if upsell_product.has_only_default_variant %}
                  <input type="hidden" name="id" value="{{ upsell_product.selected_or_first_available_variant.id }}">
                {% else %}
                  <select name="id" class="onhow-studio-upsell-variant-select">
                    {% for variant in upsell_product.variants %}
                      <option
                        value="{{ variant.id }}"
                        {% unless variant.available %}disabled{% endunless %}
                        {% if variant == upsell_product.selected_or_first_available_variant %}selected{% endif %}
                      >
                        {{ variant.title }} — {{ variant.price | money }}
                      </option>
                    {% endfor %}
                  </select>
                {% endif %}
                <button
                  type="submit"
                  class="onhow-studio-upsell-button"
                  {% unless upsell_product.selected_or_first_available_variant.available %}disabled{% endunless %}
                >
                  {% if upsell_product.selected_or_first_available_variant.available %}
                    Add to cart
                  {% else %}
                    Sold out
                  {% endif %}
                </button>
              </form>
            </div>
          </div>
        {% endif %}
      {% endfor %}
    </div>
  </div>

  <script>
    (function () {
      document.addEventListener('change', function (e) {
        if (!e.target.classList.contains('onhow-studio-upsell-variant-select')) return;
        const form = e.target.closest('.onhow-studio-upsell-form');
        const btn = form && form.querySelector('.onhow-studio-upsell-button');
        if (!btn) return;
        const selected = e.target.options[e.target.selectedIndex];
        btn.disabled = selected.disabled;
        btn.textContent = selected.disabled ? 'Sold out' : 'Add to cart';
      });

      document.addEventListener('submit', function (e) {
        if (!e.target.classList.contains('onhow-studio-upsell-form')) return;
        e.preventDefault();

        const form = e.target;
        const btn = form.querySelector('.onhow-studio-upsell-button');
        const originalText = btn.innerText;
        const variantId = Number(new FormData(form).get('id'));
        const sectionId = (document.querySelector('cart-items-component[data-drawer]') || {}).dataset?.sectionId;

        btn.disabled = true;
        btn.innerText = 'Adding...';

        const payload = { items: [{ id: variantId, quantity: 1 }] };
        if (sectionId) payload.sections = sectionId;

        fetch(
          (window.Shopify && window.Shopify.routes && window.Shopify.routes.root
            ? window.Shopify.routes.root
            : '/') + 'cart/add.js',
          {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json',
              'X-Requested-With': 'XMLHttpRequest',
            },
            body: JSON.stringify(payload),
          }
        )
          .then(function (r) {
            if (!r.ok) throw new Error('add failed');
            return r.json();
          })
          .then(function (data) {
            btn.innerText = 'Added ✓';
            btn.disabled = true;

            document.dispatchEvent(
              new CustomEvent('cart:update', {
                bubbles: true,
                detail: {
                  resource: data,
                  sourceId: 'onhow-studio-upsell',
                  data: {
                    sections: data.sections || {},
                  },
                },
              })
            );

            var drawer = document.querySelector('cart-drawer-component');
            if (drawer && typeof drawer.open === 'function') drawer.open();
          })
          .catch(function () {
            btn.innerText = 'Error';
            setTimeout(function () {
              btn.innerText = originalText;
              btn.disabled = false;
            }, 2000);
          });
      });
    })();
  </script>
{% endif %}

{% comment %}
<!-- Designed by ONHOW Studio YouTube Channel - Anas El Medlaoui -->
{% endcomment %}