Where you can achieve what your competitors can’t


Upsell Widget 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 %}

{% if product.metafields.custom.complementary_products.value != blank %}

<style>
.onhow-studio-comp-container {
  margin: 20px 0;
  border-top: 1px solid rgba(var(--color-foreground, 0, 0, 0), 0.1);
  border-bottom: 1px solid rgba(var(--color-foreground, 0, 0, 0), 0.1);
  font-family: inherit;
}
.onhow-studio-comp-details {
  width: 100%;
}
.onhow-studio-comp-summary {
  cursor: pointer;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 13px 14px;
  font-size: 1.1em;
  font-weight: 600;
  list-style: none;
  color: inherit;
  background-color: color-mix(in srgb, currentColor 5%, transparent);
  border-radius: 6px;
  border-left: 3px solid color-mix(in srgb, currentColor 20%, transparent);
}
.onhow-studio-comp-summary::-webkit-details-marker {
  display: none;
}
.onhow-studio-comp-icon {
  width: 22px;
  height: 22px;
  flex-shrink: 0;
  transition: transform 0.5s ease;
}
.onhow-studio-comp-details[open] .onhow-studio-comp-icon {
  transform: rotate(180deg);
}
@keyframes onhow-comp-open {
  from {
    opacity: 0;
    transform: translateY(-8px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
@keyframes onhow-comp-close {
  from {
    opacity: 1;
    transform: translateY(0);
  }
  to {
    opacity: 0;
    transform: translateY(-8px);
  }
}
.onhow-studio-comp-details[open] .onhow-studio-comp-grid {
  animation: onhow-comp-open 0.45s ease forwards;
}
.onhow-studio-comp-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
  gap: 15px;
  padding-top: 5px;
  padding-bottom: 20px;
}
.onhow-studio-comp-card {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.onhow-studio-comp-image-link {
  display: block;
  position: relative;
  width: 100%;
  padding-bottom: 100%;
  background-color: rgba(var(--color-foreground, 0, 0, 0), 0.03);
  border-radius: min(var(--border-radius, 4px), 4px);
  overflow: hidden;
  text-decoration: none;
}
.onhow-studio-comp-image,
.onhow-studio-comp-placeholder {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.3s ease;
}
.onhow-studio-comp-card:hover .onhow-studio-comp-image {
  transform: scale(1.03);
}
.onhow-studio-comp-placeholder {
  fill: rgba(var(--color-foreground, 0, 0, 0), 0.2);
}
.onhow-studio-comp-title {
  font-size: 0.9em;
  line-height: 1.3;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  color: inherit;
  text-decoration: none;
}
.onhow-studio-comp-price {
  font-size: 0.9em;
  font-weight: 700;
  color: inherit;
}
.onhow-studio-comp-price-compare {
  font-weight: 400;
  font-size: 0.85em;
  color: rgba(var(--color-foreground, 0, 0, 0), 0.6);
  margin-left: 0.3rem;
}
.onhow-studio-comp-form {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: auto;
}
.onhow-studio-comp-variant-select {
  width: 100%;
  padding: 0.4rem;
  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.8rem;
  font-family: inherit;
  color: inherit;
  appearance: auto;
  cursor: pointer;
}
.onhow-studio-comp-button {
  width: 100%;
  padding: 0.5rem;
  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.8rem;
  font-family: inherit;
  text-align: center;
  letter-spacing: inherit;
  text-transform: inherit;
  transition: opacity 0.2s ease;
  display: flex;
  align-items: center;
  justify-content: center;
}
.onhow-studio-comp-button:hover:not(:disabled) {
  opacity: 0.85;
}
.onhow-studio-comp-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-comp-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-comp-grid::-webkit-scrollbar {
    height: 4px;
  }
  .onhow-studio-comp-grid::-webkit-scrollbar-thumb {
    background-color: rgba(var(--color-foreground, 0, 0, 0), 0.15);
    border-radius: 4px;
  }
  .onhow-studio-comp-card {
    flex: 0 0 130px;
    scroll-snap-align: start;
  }
}
</style>

<div class="onhow-studio-comp-container">
  <details class="onhow-studio-comp-details" open>
    <summary class="onhow-studio-comp-summary">
      Pairs well with
      <svg class="onhow-studio-comp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
        <polyline points="6 9 12 15 18 9"></polyline>
      </svg>
    </summary>
    <div class="onhow-studio-comp-grid">
      {% for item in product.metafields.custom.complementary_products.value %}
        {% if item.available %}
          <div class="onhow-studio-comp-card">
            <a href="{{ item.url }}" class="onhow-studio-comp-image-link">
              {% if item.featured_image %}
                <img
                  class="onhow-studio-comp-image"
                  src="{{ item.featured_image | image_url: width: 400 }}"
                  alt="{{ item.title | escape }}"
                  loading="lazy"
                  width="400"
                  height="400"
                >
              {% else %}
                {{ 'product-1' | placeholder_svg_tag: 'onhow-studio-comp-placeholder' }}
              {% endif %}
            </a>
            <a href="{{ item.url }}" class="onhow-studio-comp-title">{{ item.title }}</a>
            <div class="onhow-studio-comp-price">
              {% if item.compare_at_price > item.price %}
                <span>{{ item.price | money }}</span>
                <s class="onhow-studio-comp-price-compare">{{ item.compare_at_price | money }}</s>
              {% else %}
                <span>{{ item.price | money }}</span>
              {% endif %}
            </div>
            <form class="onhow-studio-comp-form">
              {% if item.has_only_default_variant %}
                <input type="hidden" name="id" value="{{ item.selected_or_first_available_variant.id }}">
              {% else %}
                <select name="id" class="onhow-studio-comp-variant-select">
                  {% for variant in item.variants %}
                    <option
                      value="{{ variant.id }}"
                      {% unless variant.available %}disabled{% endunless %}
                      {% if variant == item.selected_or_first_available_variant %}selected{% endif %}
                    >
                      {{ variant.title }} — {{ variant.price | money }}
                    </option>
                  {% endfor %}
                </select>
              {% endif %}
              <button
                type="submit"
                class="onhow-studio-comp-button"
                {% unless item.selected_or_first_available_variant.available %}disabled{% endunless %}
              >
                {% if item.selected_or_first_available_variant.available %}
                  Add to cart
                {% else %}
                  Sold out
                {% endif %}
              </button>
            </form>
          </div>
        {% endif %}
      {% endfor %}
    </div>
  </details>
</div>

<script>
  (function () {
    document.querySelectorAll('.onhow-studio-comp-summary').forEach(function (summary) {
      summary.addEventListener('click', function (e) {
        var details = summary.closest('.onhow-studio-comp-details');
        if (!details || !details.open) return;
        e.preventDefault();
        var grid = details.querySelector('.onhow-studio-comp-grid');
        if (!grid) { details.removeAttribute('open'); return; }
        grid.style.animation = 'onhow-comp-close 0.4s ease forwards';
        grid.addEventListener('animationend', function () {
          grid.style.animation = '';
          details.removeAttribute('open');
        }, { once: true });
      });
    });

    document.addEventListener('change', function (e) {
      if (!e.target.classList.contains('onhow-studio-comp-variant-select')) return;
      var form = e.target.closest('.onhow-studio-comp-form');
      var btn = form && form.querySelector('.onhow-studio-comp-button');
      if (!btn) return;
      var 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-comp-form')) return;
      e.preventDefault();

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

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

      var 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-comp',
                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 %}