Go to homepage

Projects /

Products Comparison Table

A responsive table to compare and filter through multiple products.

Products Comparison Table
Check our new component library →

If you’re developing an online store with plenty of products, in all likelihood you’ve been asked to work on this feature: the comparison table. The standard approach, that works in most cases, is to use a simple HTML table element. If you have 6+ products to compare, though, things can get tricky, particularly when you try to make the whole thing responsive.

With today’s resource we wanted to provide a time-saver comparison table, specifically designed for big online stores. Our inspiration is the comparison tool we found on the beautifully designed Sony UK website. In terms of UX though, instead of letting users remove products from the list, we let them select, and filter, the ones they want to compare.

Creating the structure

The HTML structure is composed of a section.cd-products-comparison-table wrapping a <header> and a div.cd-products-table. The <header> contains the action buttons (filter and reset) while the div.cd-products-table is used to wrap the div.features (product features list) and the div.cd-products-wrapper. The latter contains an unordered list (ul.cd-products-columns) for the product list items.

<section class="cd-products-comparison-table">
      <h2>Compare Models</h2>

      <div class="actions">
         <a href="#0" class="reset">Reset</a>
         <a href="#0" class="filter">Filter</a>

   <div class="cd-products-table">
      <div class="features">
         <div class="top-info">Models</div>
         <ul class="cd-features-list">
            <li>Customer Rating</li>
            <!-- other features here -->
      </div> <!-- .features -->

      <div class="cd-products-wrapper">
         <ul class="cd-products-columns">
            <li class="product">
               <div class="top-info">
                  <div class="check"></div>
                  <img src="../img/product.png" alt="product image">
                  <h3>Sumsung Series 6 J6300</h3>
               </div> <!-- .top-info -->

               <ul class="cd-features-list">
                  <li class="rate"><span>5/5</span></li>
                  <!-- other values here -->
            </li> <!-- .product -->

            <li class="product">
               <!-- product content here -->
            </li> <!-- .product -->

            <!-- other products here -->
         </ul> <!-- .cd-products-columns -->
      </div> <!-- .cd-products-wrapper -->

      <ul class="cd-table-navigation">
         <li><a href="#0" class="prev inactive">Prev</a></li>
         <li><a href="#0" class="next">Next</a></li>
   </div> <!-- .cd-products-table -->
</section> <!-- .cd-products-comparison-table -->

Adding style

The .cd-products-wrapper has a width of 100% and overflow-x of auto; the .cd-products-columns, instead, has a width equal to the sum of all columns widths and is scrollable (because of its parent overflow property). The div.features has an absolute position and is fixed on the left side of the viewport.

.cd-products-wrapper {
  overflow-x: auto;
  /* this fixes the buggy scrolling on webkit browsers - mobile devices only - when overflow property is applied */
  -webkit-overflow-scrolling: touch;

.cd-products-table .features {
  /* fixed left column - product properties list */
  position: absolute;
  z-index: 1;
  top: 0;
  left: 0;
  width: 120px;

.cd-products-columns {
  /* products list wrapper */
  width: 1200px; /* single column width * products number */
  margin-left: 120px; /* .features width */

On big devices (viewport width greater than 1170px), the .top-fixed class is added to the .cd-products-table when user scrolls down to fix the products top information (product name and image):

@media only screen and (min-width: 1170px) {
  .cd-products-table.top-fixed .cd-products-columns > li {
    padding-top: 160px;

  .cd-products-table.top-fixed .top-info {
    height: 160px;
    position: fixed;
    top: 0;

  .cd-products-table.top-fixed .top-info h3 {
    transform: translateY(-116px);
  .cd-products-table.top-fixed .top-info img {
    transform: translateY(-62px) scale(0.4);


Events handling

To implement the products table, we created a productsTable object and used the bindEvents function to attach event handlers to the proper elements:

function productsTable( element ) {
   this.element = element;
   this.table = this.element.children('.cd-products-table');
   this.productsWrapper = this.table.children('.cd-products-wrapper');
   this.tableColumns = this.productsWrapper.children('.cd-products-columns');
   this.products = this.tableColumns.children('.product');
   //additional properties here
   // bind table events

productsTable.prototype.bindEvents = function() {
   var self = this;

   self.productsWrapper.on('scroll', function(){
      //detect scroll left inside products table

   self.products.on('click', '.top-info', function(){
      //add/remove .selected class to products 

   self.filterBtn.on('click', function(event){
      //filter products
   //reset product selection
   self.resetBtn.on('click', function(event){
      //reset products visibility

   this.navigation.on('click', 'a', function(event){
      //scroll inside products table - left/right arrows

var comparisonTables = [];
   //create a productsTable object for each .cd-products-comparison-table
   comparisonTables.push(new productsTable($(this)));

You may have noticed that we added an event listener to the scroll event of the .cd-products-wrapper; when the .top-fixed class is added to the .cd-products-table, the .top-info elements are in fixed position, so they don't scroll with the .cd-products-columns. The updateLeftScrolling() function is used to horizontally translate these elements while the user scrolls inside the .cd-products-wrapper.

productsTable.prototype.updateLeftScrolling = function() {
   var scrollLeft = this.productsWrapper.scrollLeft();

   if( this.table.hasClass('top-fixed') && checkMQ() == 'desktop') setTranformX(this.productsTopInfo, '-'+scrollLeft);

Project duplicated

Project created

Globals imported

There was an error while trying to export your project. Please try again or contact us.