How to Add a Quantity Selector to Your Shopify Product Page

Step-by-Step for Non-Coders
By Marcel Reperich
Updated on Aug 03, 2025 0 min read

When running a Shopify store, creating a seamless and intuitive shopping experience is key to increasing conversions and customer satisfaction. One often-overlooked feature is the quantity selector—a simple UI element that allows shoppers to choose how many of a particular item they want to purchase before adding it to their cart.

In this post, we’ll explore why a quantity selector is important, where it makes the most sense to use it, and how it can impact your store’s performance.

Why Add a Quantity Selector?

A quantity selector gives customers more control over their purchase. Whether they want to buy multiples of a product for themselves or are purchasing items in bulk, this small feature can make the process quicker and more intuitive. It’s especially important for products that are:

  • Frequently bought in multiples (e.g., food items, party supplies)
  • Part of bundles or kits
  • Offered at discounted rates for bulk purchases

Adding a quantity selector ensures your store looks professional and functions efficiently for customers with different buying needs.

How to Add a Quantity Selector to Your Shopify Theme

                    <product-detail-quantities class="prd-DetailQuantities_Rows">
  <fieldset class="prd-DetailQuantities_Row" data-product-detail-quantities-el="option" data-name="menge">
    <legend class="prd-DetailQuantities_Legend">
      Menge:
      <span class="prd-DetailQuantities_Indicator" data-product-detail-quantities-el="indicator">1</span>
    </legend>

    <product-detail-progress-bar class="prd-Detail_ProgressBar" aria-hidden="false">
      <ul class="prd-ProgressBar" data-quantity="1" data-product-detail-progress-bar-el="list">
        <li class="prd-ProgressBar_Item" data-product-detail-progress-bar-el="item" data-quantity="1" aria-current="true"></li>
        <li class="prd-ProgressBar_Item" data-product-detail-progress-bar-el="item" data-quantity="2" aria-current="false">
          <div class="prd-ProgressBarItem_TextContainer">
            <span class="prd-ProgressBarItem_Text prd-ProgressBarItem_Text-active">
              🤑 du sparst 
              <span data-product-progress-discount-amount>€0,00
              </span>
            </span>
            <span class="prd-ProgressBarItem_Text prd-ProgressBarItem_Text-inactive">
              Spare
              <span data-product-progress-discount-amount>€0,00</span>
            </span>
          </div>
        </li>
        <li class="prd-ProgressBar_Item" data-product-detail-progress-bar-el="item" data-quantity="3" aria-current="false">
          <div class="prd-ProgressBarItem_TextContainer">
            <span class="prd-ProgressBarItem_Text prd-ProgressBarItem_Text-active">
              🤑 du sparst 
              <span data-product-progress-discount-amount>€0,00
              </span>
            </span>
            <span class="prd-ProgressBarItem_Text prd-ProgressBarItem_Text-inactive">
              Spare
              <span data-product-progress-discount-amount>€0,00</span>
            </span>
          </div>
        </li>
      </ul>
    </product-detail-progress-bar>

    <div class="prd-DetailQuantities_Options">
      <div class="prd-DetailQuantities_Option prd-DetailQuantities_Option-radio" data-product-quantity-option-radio>
        <input class="prd-DetailQuantities_Input" 
               type="radio" 
               id="menge-1" 
               name="menge" 
               value="1" 
               data-units="1" 
               data-discount="0" 
               data-default-discount="0" 
               data-product-detail-quantities-el="option-choice" 
               data-hide-recharge="false" 
               checked>
        <label class="prd-DetailQuantities_Button" for="menge-1">
          <span class="prd-DetailQuantities_Units">1 Stück</span>
        </label>
      </div>

      <div class="prd-DetailQuantities_Option prd-DetailQuantities_Option-radio" data-product-quantity-option-radio>
        <input class="prd-DetailQuantities_Input" 
               type="radio" 
               id="menge-2" 
               name="menge" 
               value="2" 
               data-units="" 
               data-discount="0.0" 
               data-default-discount="0.0" 
               data-product-detail-quantities-el="option-choice" 
               data-hide-recharge="false">
        <label class="prd-DetailQuantities_Button" for="menge-2">
          <span class="prd-DetailQuantities_Units"> Stück</span>
          <div class="prd-DetailQuantities_Discount" aria-hidden="false">
            <span class="prd-DetailQuantities_DiscountText">Spare</span>
            <span class="prd-DetailQuantities_DiscountPercentage" data-product-quantity-discount-percentage>
              %
            </span>
          </div>
        </label>
      </div>

      <div class="prd-DetailQuantities_Option prd-DetailQuantities_Option-radio" data-product-quantity-option-radio>
        <input class="prd-DetailQuantities_Input" 
               type="radio" 
               id="menge-3" 
               name="menge" 
               value="3" 
               data-units="" 
               data-discount="0.0" 
               data-default-discount="0.0" 
               data-product-detail-quantities-el="option-choice" 
               data-hide-recharge="false">
        <label class="prd-DetailQuantities_Button" for="menge-3">
          <span class="prd-DetailQuantities_Units"> Stück</span>
          <div class="prd-DetailQuantities_Discount" aria-hidden="false">
            <span class="prd-DetailQuantities_DiscountText">Spare</span>
            <span class="prd-DetailQuantities_DiscountPercentage" data-product-quantity-discount-percentage>
              %
            </span>
          </div>
        </label>
      </div>
    </div>
  </fieldset>
</product-detail-quantities>

<style>

.prd-DetailQuantities_Rows {
    display: block;
    margin-bottom: 20px;
    margin-top: 15px
}

.prd-DetailQuantities_Row {
    border: 0;
    margin: 0;
    padding: 0
}

.prd-DetailQuantities_RowTop {
    align-items: center;
    display: flex;
    justify-content: space-between
}

.prd-DetailQuantities_Legend {
    grid-gap: 5px;
    align-items: baseline;
    color: #000;
    display: flex;
    gap: 5px;
    line-height: 1;
    padding-left: 0;
    padding-right: 0
}

.prd-DetailQuantities_Legend .prd-DetailQuantities_Indicator,.prd-DetailQuantities_Legend .prd-DetailQuantities_Title {
    color: var(--text-color);
    display: block;
    font-family: Gordita Bold;
    font-size: 14px
}

.prd-DetailQuantities_Options {
    grid-gap: 10px;
    align-items: center;
    display: grid;
    gap: 10px;
    grid-template-columns: repeat(3,1fr);
    margin-top: 15px;
    position: relative
}

.prd-DetailQuantities_Option {
    height: 100%
}

.prd-DetailQuantities_Option input {
    display: none
}

.prd-DetailQuantities_Button {
    grid-gap: 2px;
    align-items: center;
    background-color: #fff;
    border: 1px solid #ddd;
    border-radius: 50px;
    cursor: pointer;
    display: flex;
    flex-direction: column;
    font-size: .8em;
    gap: 2px;
    height: 100%;
    justify-content: center;
    min-width: 60px;
    padding: 10px;
    position: relative;
    text-align: center;
    transition: border-color .3s ease
}

.prd-DetailQuantities_Button:hover {
    border-color: #1c1c1c
}

.prd-DetailQuantities_Button-soldOut {
    background-color: #f4f4f5;
    border: 1px solid #d8d8d8
}

.prd-DetailQuantities_Units {
    display: block;
    font-size: 11px;
    line-height: 1;
    text-transform: capitalize
}

.prd-DetailQuantities_Discount {
    grid-gap: 5px;
    display: flex;
    gap: 5px;
    line-height: 1
}

.prd-DetailQuantities_Discount[aria-hidden=true] {
    display: none
}

.prd-DetailQuantities_Discount-reversed {
    flex-direction: row-reverse
}

.prd-DetailQuantities_DiscountPercentage,.prd-DetailQuantities_DiscountText {
    color: #008B13;
    display: flex;
    font-size: 11px;
    line-height: 1;
    text-transform: capitalize
}

.prd-DetailQuantities_Input:checked+.prd-DetailQuantities_Button {
    border-color: #1c1c1c
}

.prd-DetailQuantities_Input:checked+.prd-DetailQuantities_Button:before {
    background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg width='18' height='18' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='18' height='18' rx='9' fill='%23000'/%3E%3Cpath d='M7.333 12.5 4 9.521 5.167 8.48l2.166 1.936 5.5-4.915L14 6.543 7.333 12.5Z' fill='%23fff'/%3E%3C/svg%3E");
    content: "";
    height: 18px;
    position: absolute;
    right: 0;
    top: -8px;
    width: 18px;
    z-index: 1
}

.prd-Detail_ProgressBar {
    display: block;
    margin-bottom: 15px;
    margin-top: 15px
}

.prd-Detail_ProgressBar[aria-hidden=true] {
    display: none
}

.prd-ProgressBar {
    grid-gap: 10px;
    align-items: center;
    background-color: #e8e8e8;
    border-radius: 5px;
    display: grid;
    gap: 10px;
    grid-template-columns: repeat(3,1fr);
    height: 4px;
    list-style: none;
    margin-top: 15px;
    position: relative
}

.prd-ProgressBar[data-quantity="1"]:after {
    width: calc(33.33333% - 8px)
}

.prd-ProgressBar[data-quantity="2"]:after {
    width: calc(66.66667% - 5px)
}

.prd-ProgressBar[data-quantity="3"]:after {
    width: 100%
}

.prd-ProgressBar:after {
    background-color: #008B12;
    border-radius: 5px;
    content: "";
    height: 4px;
    left: 0;
    position: absolute;
    top: 0;
    transition: inline-size .1s ease-in-out
}

.prd-ProgressBar_Item {
    position: relative
}

.prd-ProgressBar_Item:after,.prd-ProgressBar_Item:before {
    content: "";
    left: 50%;
    opacity: 0;
    position: absolute;
    transition: opacity .2s ease-in-out
}

.prd-ProgressBar_Item:before {
    border-bottom: 12px solid #008B13;
    bottom: 0
}

.prd-ProgressBar_Item:after,.prd-ProgressBar_Item:before {
    border-left: 6px solid transparent;
    border-right: 6px solid transparent;
    transform: translate(-50%)
}

.prd-ProgressBar_Item:after {
    border-top: 12px solid #e8e8e8;
    top: 0
}

.prd-ProgressBar_Item[data-quantity="2"]:after,.prd-ProgressBar_Item[data-quantity="2"]:before,.prd-ProgressBar_Item[data-quantity="3"]:after,.prd-ProgressBar_Item[data-quantity="3"]:before {
    opacity: 0
}

.prd-ProgressBar_Item[data-quantity="2"][aria-current=true],.prd-ProgressBar_Item[data-quantity="2"][aria-current=true]:before,.prd-ProgressBar_Item[data-quantity="3"][aria-current=true],.prd-ProgressBar_Item[data-quantity="3"][aria-current=true]:before {
    opacity: 1
}

.prd-ProgressBar_Item[data-quantity="2"][aria-current=true]:after,.prd-ProgressBar_Item[data-quantity="3"][aria-current=true]:after {
    opacity: 0
}

[data-quantity="1"] .prd-ProgressBar_Item[data-quantity="2"],[data-quantity="1"] .prd-ProgressBar_Item[data-quantity="3"] {
    opacity: 1
}

[data-quantity="1"] .prd-ProgressBar_Item[data-quantity="2"]:before,[data-quantity="1"] .prd-ProgressBar_Item[data-quantity="3"]:before {
    opacity: 0
}

[data-quantity="1"] .prd-ProgressBar_Item[data-quantity="2"]:after,[data-quantity="1"] .prd-ProgressBar_Item[data-quantity="3"]:after,[data-quantity="2"] .prd-ProgressBar_Item[data-quantity="2"][data-quantity="3"]:after,[data-quantity="2"] .prd-ProgressBar_Item[data-quantity="3"][data-quantity="3"]:after {
    opacity: 1
}

[data-quantity="3"] .prd-ProgressBar_Item[data-quantity="2"][data-quantity="2"],[data-quantity="3"] .prd-ProgressBar_Item[data-quantity="3"][data-quantity="2"] {
    opacity: 0
}

.prd-ProgressBarItem_TextContainer {
    bottom: 32px;
    left: 0;
    opacity: 0;
    position: absolute;
    transition: opacity .2s ease-in-out;
    width: 100%
}

.prd-ProgressBarItem_TextContainer .prd-ProgressBarItem_Text {
    display: block;
    font-size: 10px;
    height: 0;
    left: 50%;
    opacity: 0;
    position: absolute;
    text-align: center;
    top: 50%;
    transform: translate(-50%,-50%);
    width: -webkit-max-content;
    width: -moz-max-content;
    width: max-content
}

.prd-ProgressBarItem_TextContainer .prd-ProgressBarItem_Text-inactive,[aria-current=true] .prd-ProgressBarItem_TextContainer,[aria-current=true] .prd-ProgressBarItem_TextContainer .prd-ProgressBarItem_Text-active {
    opacity: 1
}

[aria-current=true] .prd-ProgressBarItem_TextContainer .prd-ProgressBarItem_Text-inactive {
    opacity: 0
}

[aria-current=false] .prd-ProgressBarItem_TextContainer {
    opacity: .5
}

</style>

<script>
// Option 1: Using DOMContentLoaded
document.addEventListener('DOMContentLoaded', () => {
  class ProductDetailQuantities extends HTMLElement {
    constructor() {
      super();
      this.init();
    }

    init() {
      const inputs = this.querySelectorAll('[data-product-detail-quantities-el="option-choice"]');
      const indicator = this.querySelector('[data-product-detail-quantities-el="indicator"]');
      const progressItems = this.querySelectorAll('[data-product-detail-progress-bar-el="item"]');
      const progressBar = this.querySelector('[data-product-detail-progress-bar-el="list"]');
      const form = document.querySelector('form[action="/cart/add"]');

      let quantityInput = form.querySelector('input[name="quantity"]');
      if (!quantityInput) {
        quantityInput = document.createElement('input');
        quantityInput.type = 'hidden';
        quantityInput.name = 'quantity';
        quantityInput.value = '1';
        form.appendChild(quantityInput);
      }

      inputs.forEach(input => {
        input.addEventListener('change', () => {
          if (input.checked) {

            if (indicator) {
              indicator.textContent = input.dataset.units;
            }

            if (progressBar) {
              progressBar.setAttribute('data-quantity', input.value);
            }

            progressItems.forEach(item => {
              const isCurrentItem = item.dataset.quantity === input.value;
              item.setAttribute('aria-current', isCurrentItem);
            });

            if (quantityInput) {
              quantityInput.value = input.dataset.units;
              quantityInput.dispatchEvent(new Event('change', { bubbles: true }));
            }

            document.querySelectorAll('form[action="/cart/add"]').forEach(cartForm => {
              let formQuantityInput = cartForm.querySelector('input[name="quantity"]');
              if (!formQuantityInput) {
                formQuantityInput = document.createElement('input');
                formQuantityInput.type = 'hidden';
                formQuantityInput.name = 'quantity';
                cartForm.appendChild(formQuantityInput);
              }
              formQuantityInput.value = input.dataset.units;
              formQuantityInput.dispatchEvent(new Event('change', { bubbles: true }));
            });
          }
        });
      });
    }
  }

  customElements.define('product-detail-quantities', ProductDetailQuantities);
});
</script>