(Squarespace) Toggle between products in Shop Page

Description: Click on each category link on Shop Page >> Filter corresponding product items under without opening new page.

Note: This will work with 200 products or under only You can also use Filter Plugin, so you can achieve same easier, and no limit products.

You can also use UF plugin, so you can achieve this easier for all products without complex code.

#1. First, make sure you set Category to Top.

#2. Next, use this code to Store Page Header Injection

<script>
document.addEventListener('DOMContentLoaded', function() {
  
  setTimeout(function() {
    initCategoryFilter();
  }, 1000);
  
  function initCategoryFilter() {
    const categoryLinks = document.querySelectorAll('.nested-category-breadcrumb-link');
    
    categoryLinks.forEach(link => {
      link.addEventListener('click', function(e) {
        e.preventDefault();
        
        const categoryName = this.textContent.trim();
        const categoryUrl = this.getAttribute('href');
        
        setActiveCategory(this);
        
        if (categoryName === 'All') {
          showAllProducts();
        } else {
          filterProductsByCategory(categoryUrl, categoryName);
        }
        
        updateURL(categoryUrl);
      });
    });
  }
  
  function setActiveCategory(clickedLink) {
    document.querySelectorAll('.nested-category-breadcrumb-link').forEach(link => {
      link.classList.remove('bold');
    });
    
    clickedLink.classList.add('bold');
  }
  
  function showAllProducts() {
    const productItems = document.querySelectorAll('.product-list-item');
    
    productItems.forEach((item, index) => {
      setTimeout(() => {
        item.style.display = 'block';
        item.style.animation = 'fadeInProduct 0.3s ease-out';
      }, index * 50);
    });
    
    updateProductCount();
  }
  
  async function filterProductsByCategory(categoryUrl, categoryName) {
    showLoader();
    
    try {
      const response = await fetch(categoryUrl + '?format=json');
      const data = await response.json();
      
      const categoryProductIds = data.items ? data.items.map(item => {
        const urlParts = item.fullUrl.split('/');
        return urlParts[urlParts.length - 1];
      }) : [];
      
      const allProductItems = document.querySelectorAll('.product-list-item');
      
      allProductItems.forEach((item, index) => {
        const productLink = item.querySelector('.product-list-item-link');
        const productUrl = productLink.getAttribute('href');
        const productId = productUrl.split('/').pop();
        
        const shouldShow = categoryProductIds.some(id => 
          productUrl.includes(id) || 
          productId.includes(id) ||
          id.includes(productId)
        );
        
        setTimeout(() => {
          if (shouldShow) {
            item.style.display = 'block';
            item.style.animation = 'fadeInProduct 0.3s ease-out';
          } else {
            item.style.animation = 'fadeOutProduct 0.2s ease-out';
            setTimeout(() => {
              item.style.display = 'none';
            }, 200);
          }
        }, index * 30);
      });
      
      setTimeout(() => {
        updateProductCount();
        hideLoader();
      }, 500);
      
    } catch (error) {
      console.log('Error filtering products:', error);
      hideLoader();
    }
  }
  
  function updateProductCount() {
    const visibleProducts = document.querySelectorAll('.product-list-item[style*="display: block"], .product-list-item:not([style*="display: none"])').length;
    
    let countElement = document.querySelector('.product-count');
    if (!countElement) {
      countElement = document.createElement('div');
      countElement.className = 'product-count';
      
      const container = document.querySelector('.product-list-container');
      if (container) {
        container.parentNode.insertBefore(countElement, container);
      }
    }
    
    countElement.textContent = `${visibleProducts} products`;
  }
  
  function updateURL(categoryUrl) {
    history.pushState(null, '', categoryUrl);
  }
  
  function showLoader() {
    let loader = document.querySelector('.category-filter-loader');
    if (!loader) {
      loader = document.createElement('div');
      loader.className = 'category-filter-loader';
      loader.innerHTML = '<div class="loader-spinner"></div>';
      
      const container = document.querySelector('.product-list-container');
      if (container) {
        container.appendChild(loader);
      }
    }
    
    loader.style.display = 'flex';
  }
  
  function hideLoader() {
    const loader = document.querySelector('.category-filter-loader');
    if (loader) {
      loader.style.display = 'none';
    }
  }
  
  window.addEventListener('popstate', function() {
    location.reload();
  });
  
});
</script>
<style>
@keyframes fadeInProduct {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
@keyframes fadeOutProduct {
  from {
    opacity: 1;
    transform: translateY(0);
  }
  to {
    opacity: 0;
    transform: translateY(-20px);
  }
}
.nested-category-breadcrumb-link {
  cursor: pointer !important;
  transition: all 0.2s ease !important;
}
.nested-category-breadcrumb-link:hover {
  opacity: 0.7 !important;
}
.nested-category-breadcrumb-link.bold {
  font-weight: bold !important;
  color: #333 !important;
}
.product-count {
  text-align: center !important;
  margin: 20px 0 !important;
  color: #666 !important;
  font-size: 14px !important;
  font-weight: 500 !important;
}
.category-filter-loader {
  position: absolute !important;
  top: 50% !important;
  left: 50% !important;
  transform: translate(-50%, -50%) !important;
  z-index: 100 !important;
  display: none !important;
  align-items: center !important;
  justify-content: center !important;
  background: rgba(255, 255, 255, 0.9) !important;
  width: 100% !important;
  height: 100% !important;
}
.loader-spinner {
  width: 30px !important;
  height: 30px !important;
  border: 3px solid #f3f3f3 !important;
  border-top: 3px solid #333 !important;
  border-radius: 50% !important;
  animation: spin 1s linear infinite !important;
}
@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}
.product-list-container {
  position: relative !important;
}
@media (max-width: 768px) {
  .product-count {
    font-size: 12px !important;
    margin: 15px 0 !important;
  }
  
  .loader-spinner {
    width: 25px !important;
    height: 25px !important;
  }
}
</style>

1 Like