Skip to main content

Command Palette

Search for a command to run...

Creating a Product Card with Quantity Spinner using Bootstrap 5 and jQuery

Updated
8 min read
Creating a Product Card with Quantity Spinner using Bootstrap 5 and jQuery

Based on the FoodFarm HTML template from TemplatesJungle.com, this tutorial will guide you through building a fully functional product grid where each product card includes a quantity selector and an "Add to Cart" button.

Overview

The FoodFarm template demonstrates a clean e-commerce product grid with:

  • Responsive product cards (2 to 5 columns based on screen size)

  • Product images with optional sale badges

  • Rating stars display

  • Original price with strikethrough and discounted price

  • Quantity selector with plus/minus buttons

  • Add to Cart button with cart icon

Prerequisites

Include these dependencies in your HTML:

<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" rel="stylesheet">

<!-- jQuery -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>

<!-- Bootstrap 5 JS Bundle -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>

Step 1: Create the Product Grid Container

The product grid uses Bootstrap's responsive row-cols classes to automatically adjust the number of columns:

<div class="container">
  <div class="row">
    <div class="col-md-12">
      <div class="product-grid row row-cols-2 row-cols-sm-2 row-cols-md-3 row-cols-lg-3 row-cols-xl-4 row-cols-xxl-5">
        <!-- Product cards go here -->
      </div>
    </div>
  </div>
</div>

Responsive Column Breakdown:

Breakpoint Class Columns
Extra small row-cols-2 2 columns
Small (sm) row-cols-sm-2 2 columns
Medium (md) row-cols-md-3 3 columns
Large (lg) row-cols-lg-3 3 columns
Extra large (xl) row-cols-xl-4 4 columns
Extra extra large (xxl) row-cols-xxl-5 5 columns

Step 2: Build a Single Product Card

Each product card contains the product image, badge, title, rating, pricing, quantity selector, and add to cart button.

<div class="col">
  <div class="product-item">
    <!-- Product Image with Badge -->
    <figure class="position-relative">
      <span class="badge bg-primary text-white fw-normal px-2 py-2 fs-7 position-absolute end-0 me-2">
        10% OFF
      </span>
      <a href="single-product.html" title="Product Title">
        <img src="images/product-thumbnail-1.png" alt="Product Thumbnail" class="tab-image img-fluid">
      </a>
    </figure>
    
    <!-- Product Details -->
    <div class="d-flex flex-column text-center">
      <!-- Product Title -->
      <h3 class="fs-6 fw-normal mb-0">Whole Wheat Sandwich Bread</h3>
      
      <!-- Rating Stars -->
      <div>
        <span class="rating">
          <svg width="18" height="18" class="text-warning">
            <use xlink:href="#star-full"></use>
          </svg>
          <svg width="18" height="18" class="text-warning">
            <use xlink:href="#star-full"></use>
          </svg>
          <svg width="18" height="18" class="text-warning">
            <use xlink:href="#star-full"></use>
          </svg>
          <svg width="18" height="18" class="text-warning">
            <use xlink:href="#star-full"></use>
          </svg>
          <svg width="18" height="18" class="text-warning">
            <use xlink:href="#star-half"></use>
          </svg>
        </span>
        <span>(222)</span>
      </div>
      
      <!-- Pricing -->
      <div class="d-flex justify-content-center align-items-center gap-2">
        <del>$24.00</del>
        <span class="text-dark fw-semibold">$18.00</span>
      </div>
      
      <!-- Quantity and Add to Cart Area -->
      <div class="button-area p-3 pt-0">
        <div class="row g-1">
          <!-- Quantity Selector -->
          <div class="col-12 justify-content-center d-flex mt-0">
            <div class="input-group product-qty" style="max-width: 150px;">
              <span class="input-group-btn">
                <button type="button" class="quantity-left-minus btn btn-light btn-number" data-type="minus">
                  <svg width="16" height="16">
                    <use xlink:href="#minus"></use>
                  </svg>
                </button>
              </span>
              <input type="text" name="quantity" class="quantity form-control input-number text-center" 
                     value="1" min="1" max="100">
              <span class="input-group-btn">
                <button type="button" class="quantity-right-plus btn btn-light btn-number" data-type="plus">
                  <svg width="16" height="16">
                    <use xlink:href="#plus"></use>
                  </svg>
                </button>
              </span>
            </div>
          </div>
          
          <!-- Add to Cart Button -->
          <div class="col-12">
            <a href="#" class="btn btn-primary rounded-1 p-2 fs-7 btn-cart" data-product-id="1" data-product-name="Whole Wheat Sandwich Bread" data-product-price="18.00">
              <svg width="18" height="18">
                <use xlink:href="#cart"></use>
              </svg> 
              Add to Cart
            </a>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

Step 3: Create the SVG Icon Definitions

Add these SVG icons at the beginning of your body (hidden from view):

<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
  <defs>
    <!-- Star Full Icon -->
    <symbol id="star-full" viewBox="0 0 24 24">
      <path fill="currentColor" d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/>
    </symbol>
    
    <!-- Star Half Icon -->
    <symbol id="star-half" viewBox="0 0 24 24">
      <path fill="currentColor" d="M22 9.24l-7.19-.62L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21 12 17.27 18.18 21l-1.63-7.03L22 9.24zM12 15.4V6.1l1.71 4.04 4.38.38-3.32 2.88 1 4.28L12 15.4z"/>
    </symbol>
    
    <!-- Cart Icon -->
    <symbol id="cart" viewBox="0 0 24 24">
      <path fill="currentColor" d="M7 18c-1.1 0-1.99.9-1.99 2S5.9 22 7 22s2-.9 2-2-.9-2-2-2zM1 2v2h2l3.6 7.59-1.35 2.45c-.16.28-.25.61-.25.96 0 1.1.9 2 2 2h12v-2H7.42c-.14 0-.25-.11-.25-.25l.03-.12.9-1.63h7.45c.75 0 1.41-.41 1.75-1.03l3.58-6.49c.08-.14.12-.31.12-.48 0-.55-.45-1-1-1H5.21l-.94-2H1zm16 16c-1.1 0-1.99.9-1.99 2s.89 2 1.99 2 2-.9 2-2-.9-2-2-2z"/>
    </symbol>
    
    <!-- Minus Icon -->
    <symbol id="minus" viewBox="0 0 24 24">
      <path fill="currentColor" d="M19 13H5v-2h14v2z"/>
    </symbol>
    
    <!-- Plus Icon -->
    <symbol id="plus" viewBox="0 0 24 24">
      <path fill="currentColor" d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
    </symbol>
  </defs>
</svg>

Step 4: jQuery for Quantity Spinner

The quantity spinner allows users to increase or decrease the product quantity with plus/minus buttons:

(function($) {
  "use strict";

  // Initialize quantity spinner for all product cards
  var initQuantitySpinner = function() {
    $('.product-qty').each(function() {
      var \(productQty = \)(this);
      
      // Plus button click handler
      $productQty.find('.quantity-right-plus').click(function(e) {
        e.preventDefault();
        var \(quantityInput = \)productQty.find('.quantity');
        var currentVal = parseInt($quantityInput.val());
        var maxVal = parseInt($quantityInput.attr('max'));
        
        if (!isNaN(currentVal) && currentVal < maxVal) {
          $quantityInput.val(currentVal + 1);
        }
      });
      
      // Minus button click handler
      $productQty.find('.quantity-left-minus').click(function(e) {
        e.preventDefault();
        var \(quantityInput = \)productQty.find('.quantity');
        var currentVal = parseInt($quantityInput.val());
        var minVal = parseInt($quantityInput.attr('min'));
        
        if (!isNaN(currentVal) && currentVal > minVal) {
          $quantityInput.val(currentVal - 1);
        }
      });
      
      // Manual input validation
      $productQty.find('.quantity').on('change', function() {
        var value = parseInt($(this).val());
        var min = parseInt($(this).attr('min'));
        var max = parseInt($(this).attr('max'));
        
        if (isNaN(value) || value < min) {
          $(this).val(min);
        } else if (value > max) {
          $(this).val(max);
        }
      });
    });
  };

  // Add to Cart functionality
  var initAddToCart = function() {
    $('.btn-cart').click(function(e) {
      e.preventDefault();
      
      // Get product details
      var productId = $(this).data('product-id');
      var productName = $(this).data('product-name');
      var productPrice = $(this).data('product-price');
      
      // Get quantity from the sibling quantity input
      var quantity = $(this).closest('.button-area').find('.quantity').val();
      
      // Calculate subtotal
      var subtotal = (parseFloat(productPrice) * parseInt(quantity)).toFixed(2);
      
      // Display toast notification (optional)
      showAddToCartNotification(productName, quantity, subtotal);
      
      // Store in localStorage or send to server
      addToCartStorage(productId, productName, productPrice, quantity);
      
      // Log to console for debugging
      console.log(`Added to cart: \({quantity} x \){productName} = $${subtotal}`);
    });
  };
  
  // Show toast notification
  var showAddToCartNotification = function(productName, quantity, subtotal) {
    // Check if toast container exists, create if not
    if ($('#toast-container').length === 0) {
      $('body').append('<div id="toast-container" class="position-fixed bottom-0 end-0 p-3" style="z-index: 1100;"></div>');
    }
    
    var toastHtml = `
      <div class="toast align-items-center text-white bg-success border-0 mb-2" role="alert" aria-live="assertive" aria-atomic="true" data-bs-autohide="true" data-bs-delay="3000">
        <div class="d-flex">
          <div class="toast-body">
            <strong>\({quantity}</strong> × \){productName} added to cart ($${subtotal})
          </div>
          <button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
        </div>
      </div>
    `;
    
    $('#toast-container').append(toastHtml);
    var toastElement = $('#toast-container .toast').last();
    var toast = new bootstrap.Toast(toastElement);
    toast.show();
    
    // Remove toast after hiding
    toastElement.on('hidden.bs.toast', function() {
      $(this).remove();
    });
  };
  
  // Store cart items in localStorage
  var addToCartStorage = function(productId, productName, productPrice, quantity) {
    var cart = localStorage.getItem('shoppingCart');
    cart = cart ? JSON.parse(cart) : [];
    
    // Check if product already in cart
    var existingItem = cart.find(item => item.id == productId);
    
    if (existingItem) {
      existingItem.quantity = parseInt(existingItem.quantity) + parseInt(quantity);
    } else {
      cart.push({
        id: productId,
        name: productName,
        price: parseFloat(productPrice),
        quantity: parseInt(quantity)
      });
    }
    
    localStorage.setItem('shoppingCart', JSON.stringify(cart));
    updateCartBadge();
  };
  
  // Update cart badge with total items count
  var updateCartBadge = function() {
    var cart = localStorage.getItem('shoppingCart');
    cart = cart ? JSON.parse(cart) : [];
    
    var totalItems = cart.reduce((sum, item) => sum + item.quantity, 0);
    
    if (totalItems > 0) {
      $('#cart-badge').text(totalItems).show();
    } else {
      $('#cart-badge').hide();
    }
  };

  // Document ready
  $(document).ready(function() {
    initQuantitySpinner();
    initAddToCart();
    updateCartBadge();
  });

})(jQuery);

Step 5: Complete HTML for Multiple Product Cards

Here's how to structure multiple product cards efficiently:

<div class="product-grid row row-cols-2 row-cols-sm-2 row-cols-md-3 row-cols-lg-3 row-cols-xl-4 row-cols-xxl-5">
  
  <!-- Product Card 1 - With Discount Badge -->
  <div class="col">
    <div class="product-item">
      <figure class="position-relative">
        <span class="badge bg-primary text-white fw-normal px-2 py-2 fs-7 position-absolute end-0 me-2">10% OFF</span>
        <a href="single-product.html">
          <img src="images/product-thumbnail-1.png" alt="Product" class="img-fluid">
        </a>
      </figure>
      <div class="d-flex flex-column text-center">
        <h3 class="fs-6 fw-normal mb-0">Whole Wheat Sandwich Bread</h3>
        <div>
          <span class="rating"><!-- Stars here --></span>
          <span>(222)</span>
        </div>
        <div class="d-flex justify-content-center align-items-center gap-2">
          <del>$24.00</del>
          <span class="text-dark fw-semibold">$18.00</span>
        </div>
        <!-- Quantity and Add to Cart section -->
      </div>
    </div>
  </div>
  
  <!-- Product Card 2 - No Discount -->
  <div class="col">
    <div class="product-item">
      <figure>
        <a href="single-product.html">
          <img src="images/product-thumbnail-2.png" alt="Product" class="img-fluid">
        </a>
      </figure>
      <div class="d-flex flex-column text-center">
        <h3 class="fs-6 fw-normal mb-0">Whole Grain Oatmeal</h3>
        <div>
          <span class="rating"><!-- Stars here --></span>
          <span>(41)</span>
        </div>
        <div class="d-flex justify-content-center align-items-center gap-2">
          <del>$54.00</del>
          <span class="text-dark fw-semibold">$50.00</span>
        </div>
        <!-- Quantity and Add to Cart section -->
      </div>
    </div>
  </div>
  
  <!-- Repeat for additional products -->
  
</div>

Step 6: Optional Cart Badge in Header

Add this to your header to show cart item count:

<button type="button" class="btn btn-primary position-relative">
  <svg width="24" height="24"><use xlink:href="#cart"></use></svg>
  <span id="cart-badge" class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger" style="display: none;">
    0
    <span class="visually-hidden">items in cart</span>
  </span>
</button>

Key Bootstrap Classes Used

Class Purpose
row-cols-* Responsive column count without media queries
input-group Groups quantity buttons and input field
btn / btn-primary / btn-light Button styling
badge Sale/discount indicator
position-relative / position-absolute For badge positioning
fs-6 / fs-7 Font size utilities
fw-normal / fw-semibold Font weight utilities
gap-2 Spacing between elements

Summary

This tutorial covered building a complete product card system with:

  1. Responsive product grid using Bootstrap's row-cols classes

  2. Product cards with images, badges, ratings, and pricing

  3. Quantity selector with plus/minus buttons and validation

  4. Add to Cart functionality with localStorage persistence

  5. Toast notifications for user feedback

  6. Cart badge showing total item count

The pattern from FoodFarm Grocery Store template demonstrates a clean, maintainable approach to building e-commerce product listings that work seamlessly across all device sizes. You can find many other free eCommerce HTML templates at TemplatesJungle.com which you can use as a starter template for your projects.