Hiding Out of Stock Variants in Shopify Bundle Products (Step-by-Step Guide)

If you’ve ever built a Shopify product page with bundled products, you know how tricky variant availability can get. Each bundled product has its own set of options (like colors or sizes), and when one of those options goes out of stock, you want the storefront to reflect that immediately.

Recently, I faced this exact challenge while customizing a Shopify store that offered a Tennis Bag bundle. The bundle included two products:

  • Tennis Backpack V2
  • Tennis Bag Pro V3

Each product had multiple color options (Volt, Lava, Midnight). The problem? Some colors were completely out of stock, but Shopify was still showing them as selectable options. That meant a user could click on an out-of-stock color, get frustrated, and bounce. Not good for UX, and definitely not good for conversions.

The Challenge

Shopify variants are essentially combinations of all product options. In a simple product like a T-shirt, that might mean Size: Large / Color: Red. Each combination becomes its own variant with its own stock level and availability flag.

But in this case, the product wasn’t simple at all. It was a bundle made of two separate products, each with its own set of color choices. That meant Shopify had to generate variants like Backpack: Lava / Bag Pro: Midnight. Every possible pairing of Backpack color and Bag Pro color became its own variant.

Each of those variants in the JSON carried its own availability status, inventory count, and price. The structure made sense in theory, but it created a big challenge in practice. I wasn’t just dealing with single variants anymore — I needed to know the stock status of individual option values, like whether “Volt” for the Backpack was available at all, across potentially dozens of variant combinations.

This complexity made it clear that the default Shopify setup wasn’t enough. I needed a custom way to scan the data, detect which option values were globally unavailable, and then reflect that visually on the storefront.

The Technical Solution

After analyzing the JSON output of the product (product.variants), I noticed that every variant had an available: true/false property. This was the key.

Step 1: Check availability for each option value

For each option value (like Volt in the Backpack group), I looped through all variants and checked:

  • Does any available variant use this value in this option slot (option1, option2, or option3)?
  • If yes → leave it enabled.
  • If no → disable the input and mark it visually as “out of stock”.

Here’s the Shopify Liquid snippet I added inside the {% for value in option.values %} loop:

{%- assign has_available = false -%}
{%- for v in product.variants -%}
  {%- case option.position -%}
    {%- when 1 -%}{% if v.option1 == value and v.available %}{% assign has_available = true %}{% break %}{% endif %}
    {%- when 2 -%}{% if v.option2 == value and v.available %}{% assign has_available = true %}{% break %}{% endif %}
    {%- when 3 -%}{% if v.option3 == value and v.available %}{% assign has_available = true %}{% break %}{% endif %}
  {%- endcase -%}
{%- endfor -%}

<div class="product-variant__item {% unless has_available %}oos-option{% endunless %}">
  <input type="radio"
         name="product-{{ option.name | escape | downcase | strip }}-{{ section.id }}"
         id="{{ product.handle }}-option-{{ option.name | escape | downcase | strip }}-{{ forloop.index }}-{{ section.id }}"
         value="{{ value | escape }}"
         class="product-variant__input product-variant-value"
         {% if option.selected_value == value %}checked{% endif %}
         {% unless has_available %}disabled{% endunless %}>
  <label for="{{ product.handle }}-option-{{ option.name | escape | downcase | strip }}-{{ forloop.index }}-{{ section.id }}" class="product-variant__label">
    {{ value }}
  </label>
</div>

Step 2: Style out-of-stock options

With the logic in place, I added a simple CSS rule to strike through unavailable values:

.product-variant__item.oos-option .product-variant__label {
  text-decoration: line-through;
  opacity: 0.5;
  cursor: not-allowed;
}

Step 3: Verify across both products

  • Backpack V2 (option1):
    • Volt ❌ disabled (no available variants)
    • Midnight ❌ disabled (no available variants)
    • Lava ✅ enabled
  • Bag Pro V3 (option2):
    • All colors ✅ enabled right now (because they’re still in stock).
    • If one day Volt for Pro V3 goes out of stock globally, the same code will automatically strike it through.

Final Thoughts

This was a great reminder that sometimes the hardest part is not the code, but the logic. Once I understood that each color’s availability depended on whether any variant containing it was available, the solution became straightforward.

If you’re working on a Shopify store with bundle products or complex variant logic, take the time to dig into your product.variants JSON. It has all the answers you need to build smarter, more user-friendly product pages.

Hire me for custom Shopify Development.

more insights