JSFiddle - React, Tailwind, and code Playground

HTML

<div class="table-wrapper">
  <table>
    <thead>
      <tr>
        <th colspan="2">Vehicles</th>
      </tr>
    </thead>

    <tbody>
      <tr>
        <th colspan="2" class="title">Cars</th>
      </tr>
      <tr>
        <td>Opel</td>
        <td>Corsa</td>
      </tr>
      <tr>
        <td>Renault</td>
        <td>Clio</td>
      </tr>
      <tr>
        <td>Citroen</td>
        <td>AX</td>
      </tr>

      <tr>
        <th colspan="2" class="title">Motorbikes</th>
      </tr>
      <tr>
        <td>Honda</td>
        <td>Hornet</td>
      </tr>
      <tr>
        <td>Yamaha</td>
        <td>Virago</td>
      </tr>
      <tr>
        <td>Suzuki</td>
        <td>GSXR</td>
      </tr>

      <tr>
        <th colspan="2" class="title">Planes</th>
      </tr>
      <tr>
        <td>Boeing</td>
        <td>737</td>
      </tr>
      <tr>
        <td>Boeing</td>
        <td>767</td>
      </tr>
      <tr>
        <td>Boeing</td>
        <td>777</td>
      </tr>
    </tbody>
  </table>
</div>

CSS

.table-wrapper {
  height: 180px;
  overflow: hidden;
}

table {
  font: arial;
  text-align: left;
  width: 300px;
}

tbody {
  display: block;
  height: 80px;
  overflow: auto;
  width: 100%;
}

JavaScript

//start scrolling
$('tbody').scroll(function() {
  doStuff();
});

//initialize headers
var headers = [];
$('.title').each(function(index) {
  var header = {
    thead: $(this),
    top: $(this).parent().index() * $(this).outerHeight(),
    bottom: 0
  };
  headers.push(header);
});

//setup the first one
headers[0].thead.addClass('active');
$('thead').find('.active').remove();
$('.active').last().clone().appendTo($('thead'));

//calculate header container heights
for (var i = 0; i < headers.length; ++i) {
  	headers[i].bottom = (i+1 < headers.length) ? headers[i+1].top : 100000;
}

var headerIndex = 0;
function doStuff() {
	var scroll = $('tbody').scrollTop();
  if (scroll >= headers[headerIndex].top 
  	&& scroll < headers[headerIndex].bottom) {
  	return true;
  }
  else {
  	//get new headerindex and update header
    headers[headerIndex].thead.removeClass('active');
    headerIndex = (scroll < headers[headerIndex].top) ? headerIndex-1 : headerIndex+1;
    headers[headerIndex].thead.addClass('active');
    
    $('thead').find('.active').remove();
    $('.active').last().clone().appendTo($('thead'));
  }
}