Sticky table header with two header rows
Asked Answered
U

3

29

Considering the following table structure:

<table border="1px">
  <thead>
    <tr>
        <td rowspan="2">Item No</td>
        <th colspan="2">Store ABC</th>
        <th colspan="2">Store DEF</th>
    </tr>
    <tr>
        <th>Baskets</th>
        <th>Customers</th>
        <th>Baskets</th>
        <th>Customers</th>
    </tr>
  </thead>
  <tbody>
  </tbody>
</table>

I would like to make the table-header sticky so that when scrolling down the table, it remains visible.

Easy thing when there is only one header row. However, things get tricky when having two. I've come up with the following css:

thead th, thead td {
  position: sticky;
  top: 0;
  background: #eee;
}

It "almost" works, except when scrolling the table, the two header rows kind of "slip" to the same position, the second row coming on top of the first one.

How to make the two header rows sticky in a way that their appearance is not changed when scrolling down the table (i.e. the whole header remains a "block" and its two distinct rows stay in place)?

Mandatory jsFiddle: http://jsfiddle.net/juyvcLd6/3/

--- UPDATE

Identifying the two header rows with class="first" and class="second" respectively, plus adding the following css:

thead tr.first th, thead tr.first td {
  position: sticky;
  top: 0;
  background: #eee;
}

thead tr.second th, thead tr.second td {
  position: sticky;
  top: 17px;
  background: #eee;
}

has the effect of keeping the header rows together in place while scrolling the table. However, the top: 17px element is very much dependent on the actual rendered size of the row. If the user has its browser rendering text sizes differently, for instance, then everything will go off.

Plus, this method has the drawback of getting rid of header borders in quite a weird way.

How to make sure that, when scrolling the table:

  1. the top: xxx rule for the second row actually reflects the real height of the first row?
  2. the borders stay consistent with what they look like when the table is not scrolled?

See updated jsFiddle: http://jsfiddle.net/juyvcLd6/4/

Unclothe answered 14/4, 2019 at 9:39 Comment(2)
Your tr.first and tr.second approach saved the day. Thanks!Samirasamisen
In most cases, it's totally workable to accept a fixed height for some of your header cells, and this type of solution works like a charm, without need for gross JS hacks.Deil
J
19

Since all your header rows are in thead, just apply the position: sticky; to thead instead of thead th, thead td. In this way you don't need to worry about the height of each header row or how many header row you have.

thead {
  position: sticky;
  top: 0;
  background: #eee;
}

I made the change to your first example and it works. https://jsfiddle.net/qcuxkng5/

table {
  height: 100%;
  border-collapse: collapse;
  width: 100%;
  margin: 10px;
  font-size: 0.8em;
}

thead {
  position: sticky;
  top: 0;
  background: #eee;
}
<table border="1px">
  <thead>
    <tr>
        <td rowspan="2">Item No</td>
        <th colspan="2">Store ABC</th>
        <th colspan="2">Store DEF</th>
    </tr>
    <tr>
        <th>Baskets</th>
        <th>Customers</th>
        <th>Baskets</th>
        <th>Customers</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
  </tbody>
</table>
Joyance answered 15/12, 2021 at 23:20 Comment(2)
A nice simple answer to my problem, and no jQuery needed. My top header row would scroll and the 2nd one was fixed when my css was applying position: sticky; and top: 0; to the <th> elements. Putting those in a class that applies to just the <thead> as you describe, did the trick!Tilbury
This worked for me in recent versions of firefox and chromium, but not in qtwebengine (which is based on chromium).Eyed
C
12

You can remove the 'top' from thead tr.second th, thead tr.second td and add a jquery for getting and setting height of second header

    $(document).ready(function() { 
        var firstheight = $('.first').height();
        $("thead tr.second th, thead tr.second td").css("top", firstheight)
    });
    table {
       height: 100%;
       border-collapse: collapse;
       width: 100%;
       margin: 10px;
       font-size: 0.8em;
   }

   thead tr.first th, thead tr.first td {
       position: sticky;
       position: -webkit-sticky; /* Safari */
       top: 0;
       background: #eee;
   }

   thead tr.second th, thead tr.second td {
       position: sticky;
       position: -webkit-sticky; /* Safari */
       background: #eee;
   }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table border="1px">
  <thead>
    <tr class="first">
        <td rowspan="2">Item No</td>
        <th colspan="2">Store ABC</th>
        <th colspan="2">Store DEF</th>
    </tr>
    <tr class="second">
        <th>Baskets</th>
        <th>Customers</th>
        <th>Baskets</th>
        <th>Customers</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
    <tr>
      <th>123</th>
      <td>345</td>
      <td>345</td>
      <td>345</td>
      <td>345</td>
    </tr>
  </tbody>
</table>
Commemorate answered 14/4, 2019 at 10:16 Comment(1)
Interesting. Although jQuery is definitely not an option here (trying to stay clean), this approach does make sense, and is perfectly applicable with vanilla JavaScript.Unclothe
S
0

All you need to do just add style="top:20px" to second <tr> tag. to be said you can reduce or increase 20px to any number would be best for your table.

<table class="table-bordered">
        <thead>
            <tr class="sticky-top">
                <th colspan="2">Groups of 12 or less</th>
                <th colspan="3">Groups of 13 or 14</th>
            </tr>
            <tr class="sticky-top" style="top:20px">
                <th>Number of scores</th>
                <th>Average score</th>
                <th>Number of scores</th>
                <th>% of Number of scores</th>
                <th>Average score</th>
            </tr>
        </thead>
Structural answered 14/6, 2023 at 8:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.